Files
reactbin/specs/007-tag-browser/tasks.md
agatha 355014f975 Feat: Add tag browser page at /tags with count-sorted tag list and library deep-link
- Extends GET /api/v1/tags with sort=count_desc and min_count query params
- New TagsComponent at /tags (public, no auth guard) shows all tags sorted by image count
- Clicking a tag navigates to /?tags=<name> for a pre-filtered library view
- LibraryComponent reads ?tags= query param on init to support deep-linking from tag browser
- Library header gains a "Browse tags" link to /tags for discoverability
- All 15 TDD tasks complete; ruff, ng lint, and ng build clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:40:06 +00:00

8.5 KiB
Raw Blame History

Tasks: Tag Browser

Input: Design documents from specs/007-tag-browser/ Prerequisites: plan.md , spec.md , data-model.md , contracts/ , quickstart.md

Tests: TDD is non-negotiable (§5.1). Every implementation task is preceded by a failing-test task. Test tasks MUST be written and confirmed failing before the corresponding implementation task begins.

Organization: Foundational API + service changes first (block all stories), then one phase per user story.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel with other [P] tasks in the same phase
  • [Story]: Which user story this task belongs to
  • Exact file paths included in every task description

Phase 1: Setup

No new project structure required. The existing layout accommodates all changes.


Phase 2: Foundational — API Enhancement & Service Update

Purpose: Extend GET /api/v1/tags with sort and min_count query parameters; update the Angular TagService to pass them. All three user stories depend on the API returning tags sorted by count with zero-count tags excluded.

⚠️ CRITICAL: No user story work can begin until this phase is complete.

  • T001 [P] Write failing API integration tests for sort=count_desc and min_count=1 params in api/tests/integration/test_tags.py — assert response is ordered highest-count-first and excludes zero-count tags
  • T002 [P] Write failing spec for updated TagService.list() accepting sort and minCount params in ui/src/app/services/tag.service.spec.ts — final signature: list(prefix = '', limit = 100, offset = 0, sort?: string, minCount?: number)
  • T003 Extend TagRepository.list_tags() in api/app/repositories/tag_repo.py — add sort: str = "name" and min_count: int = 0 params; apply ORDER BY image_count DESC, name ASC when sort="count_desc"; apply HAVING image_count >= min_count filter — run AFTER T001 (TDD)
  • T004 Expose sort and min_count as optional query params in api/app/routers/tags.py — pass through to tag_repo.list_tags() — run AFTER T003
  • T005 Update TagService.list() in ui/src/app/services/tag.service.ts — final signature: list(prefix = '', limit = 100, offset = 0, sort?: string, minCount?: number); include sort and min_count in HttpParams when provided — run AFTER T002 (TDD)

Execution order: T001 ∥ T002 → T003 (after T001), T005 (after T002) → T004 (after T003)

Checkpoint: GET /api/v1/tags?sort=count_desc&min_count=1 returns tags sorted by image count descending with zero-count tags excluded. TagService.list() passes the new params.


Phase 3: User Story 1 — Browse All Tags (Priority: P1) 🎯 MVP

Goal: A /tags page that lists every tag (with count ≥ 1) sorted from most-used to least-used, with loading skeleton, empty state, and error state matching the existing design system.

Independent Test: Navigate to /tags while logged out. Confirm every tag with at least one image is shown with its count, ordered by count descending. Confirm the empty state appears when no tags exist.

Tests for User Story 1

  • T006 [US1] Write failing spec for TagBrowserComponent in ui/src/app/tags/tags.component.spec.ts covering: (a) skeleton shown while loading, (b) tag list rendered with name and count after load, (c) tags ordered by count descending, (d) empty state shown when tag list is empty, (e) error state shown on fetch failure with retry button, (f) each rendered tag element has an href of /?tags=<tagname> (FR-005 coverage), (g) component renders when AuthService is not present / user is unauthenticated (FR-006 coverage)

Implementation for User Story 1

  • T007 [US1] Create TagBrowserComponent in ui/src/app/tags/tags.component.ts — standalone component; on init call tagService.list('', 500, 0, 'count_desc', 1) (positional order matches T005 signature); display tag chips with name + count; each chip is a routerLink="/" with [queryParams]="{tags: tag.name}" so the href renders as /?tags=<name>; include skeleton loading state (reuse .skeleton class from global styles), empty state, and error state with retry; apply design tokens throughout
  • T008 [P] [US1] Add /tags lazy route to ui/src/app/app.routes.ts — load TagBrowserComponent; no auth guard (public route)

Checkpoint: /tags renders a sorted, filterable tag list visible without authentication.


Phase 4: User Story 2 — Navigate from Tag to Library (Priority: P1)

Goal: Clicking a tag on the tag browser navigates to the library pre-filtered to that tag. Requires the library to read ?tags=<name> from the URL on init and apply it as an active filter before the first image load.

Independent Test: Navigate directly to /?tags=cat in the browser. Confirm the library loads showing only images tagged cat and the cat chip appears in the active filter bar.

Tests for User Story 2

  • T009 [US2] Write failing spec for LibraryComponent reading ?tags= query param in ui/src/app/library/library.component.spec.ts — assert that when the component initialises with ?tags=cat in the URL, activeFilters contains ['cat'] and imageService.list is called with ['cat']

Implementation for User Story 2

  • T010 [US2] Update LibraryComponent in ui/src/app/library/library.component.ts — inject ActivatedRoute; in ngOnInit, read snapshot.queryParamMap.get('tags'); if present, split by comma, set activeFilters before calling load() so the first fetch is already filtered

Checkpoint: Navigating to /?tags=cat from the tag browser shows the correctly filtered library.


Phase 5: User Story 3 — Tag Browser Discoverable from Library (Priority: P2)

Goal: A visible "Browse tags" link in the library page header navigates to /tags. Makes the tag browser discoverable without requiring the user to type the URL.

Independent Test: Load the library page. Confirm a link to /tags is visible in the header and navigates correctly when clicked.

Tests for User Story 3

  • T011 [US3] Write failing spec for library nav link to /tags in ui/src/app/library/library.component.spec.ts — assert a link element with href="/tags" is present in the rendered header

Implementation for User Story 3

  • T012 [US3] Add "Browse tags" routerLink="/tags" link to LibraryComponent header in ui/src/app/library/library.component.ts — place alongside the existing Upload button; style consistently with the existing header button pattern

Checkpoint: All three user stories are independently functional.


Phase 6: Polish & Cross-Cutting Concerns

  • T013 [P] Run ruff check api/app/ api/tests/ and fix any violations
  • T014 [P] Run ng lint in ui/ — zero violations required
  • T015 Run ng build in ui/ — zero errors required

Dependencies & Execution Order

Phase Dependencies

  • Phase 2 (Foundational): Blocks all user story phases — must complete first
  • Phase 3 (US1): Depends on Phase 2 — TagBrowserComponent needs the sorted tag endpoint
  • Phase 4 (US2): Depends on Phase 2 — library deep-link needs no API change, but should follow US1 for coherent testing
  • Phase 5 (US3): Depends on Phase 3 (needs the /tags route to exist for the link to be meaningful)
  • Phase 6 (Polish): Depends on all prior phases

Within Phase 2

  • T001 ∥ T002 (different repos, both write failing tests)
  • T003 after T001 (TDD: failing test must exist first)
  • T005 after T002 (TDD: failing test must exist first)
  • T003 ∥ T005 (different repos, after their respective tests)
  • T004 after T003 (router wraps repo)

Execution Order (Phase 2)

Step 1 (parallel): T001, T002
Step 2 (parallel): T003 (after T001), T005 (after T002)
Step 3: T004 (after T003)

Parallel Opportunities (Phases 35)

  • T007 and T008 are parallel within Phase 3

Implementation Strategy

MVP (US1 + US2 — both P1)

  1. Complete Phase 2 (Foundational)
  2. Complete Phase 3 (US1 — TagBrowserComponent)
  3. Complete Phase 4 (US2 — library deep-link)
  4. Validate: Navigate from tag browser → library → confirm pre-filtered results
  5. Phases 56 add discoverability and polish

Incremental Delivery

  • After Phase 3: /tags page is live and usable (visitors can browse tags)
  • After Phase 4: clicking a tag works end-to-end (browse → filtered library)
  • After Phase 5: tag browser is discoverable from the library without typing the URL
  • After Phase 6: lint and build clean, ready for merge