Files
reactbin/specs/015-library-pagination/tasks.md
agatha 781be909bc Feat: Replace Load More with Previous/Next pagination in library
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>
2026-05-09 21:08:42 +00:00

7.0 KiB
Raw Permalink Blame History

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.ts covering: (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" calls imageService.list with offset=24; (8) clicking "Previous" from page 2 calls imageService.list with offset=0; (9) applying a filter resets to page 1 (offset=0). Run ng test and confirm the new tests FAIL (implementation pending).

  • T002 [US1] Update ui/src/app/library/library.component.ts: (a) change private readonly limit = 50 to private readonly limit = 24; (b) remove hasMore property and loadMore() method; (c) add properties currentPage = 1, totalPages = 1, total = 0; (d) rename/replace load() to call imageService.list(this.activeFilters, this.limit, (this.currentPage - 1) * this.limit) and on success set this.images = res.items (replace, not append), this.total = res.total, this.totalPages = Math.ceil(res.total / this.limit), clamp currentPage to Math.max(1, Math.min(this.currentPage, this.totalPages)); (e) add nextPage() that increments currentPage and calls load(); (f) add prevPage() that decrements currentPage and calls load(); (g) in applyFilter(), reset this.currentPage = 1 before calling load(); (h) replace the <button class="load-more"> element in the template with a pagination bar: a "Previous" button bound to (click)="prevPage()" disabled/hidden when currentPage === 1, a "Page {{ currentPage }} of {{ totalPages }}" span, a "Next" button bound to (click)="nextPage()" disabled/hidden when currentPage === 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". Run ng test and 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.ts covering: (1) on init with ?page=2 in queryParamMap, currentPage is set to 2 and list is called with offset=24; (2) on init with ?page=9999 and total of 48 images, currentPage is clamped to page 1; (3) nextPage() calls router.navigate with queryParams: { page: 2 } and queryParamsHandling: 'merge'; (4) applyFilter() calls router.navigate with queryParams: { page: 1 } and queryParamsHandling: 'merge'. Run ng test and confirm new tests FAIL.

  • T004 [US2] Update ui/src/app/library/library.component.ts: (a) in ngOnInit, after reading the tags param, read const pageParam = this.route.snapshot.queryParamMap.get('page') and set this.currentPage = pageParam ? Math.max(1, parseInt(pageParam, 10)) : 1 (out-of-range clamping happens after load when totalPages is known); (b) update nextPage() and prevPage() to call this.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' }) after updating currentPage; (c) update applyFilter() to call this.router.navigate([], { queryParams: { page: 1, tags: tags.join(',') || null }, queryParamsHandling: 'merge' }) when resetting to page 1 (pass null for tags to remove param when empty); (d) after load resolves and totalPages is known, clamp currentPage to Math.min(this.currentPage, Math.max(1, this.totalPages)) and if clamped, call navigate to correct the URL. Run ng test and 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 lint on ui/src/app/library/library.component.ts and fix any issues; confirm ng test passes 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)

  1. T001T002 — page navigation controls, limit change, replace append
  2. STOP and VALIDATE: open browser, confirm pagination controls appear and work
  3. Deploy if ready

Full Delivery

  1. T001T002 (US1) → validate
  2. T003T004 (US2) → validate URL state
  3. T005 (polish) → ship