Files
reactbin/specs/007-tag-browser/plan.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

5.3 KiB

Implementation Plan: Tag Browser

Branch: 007-tag-browser | Date: 2026-05-06 | Spec: spec.md Input: Feature specification from specs/007-tag-browser/spec.md

Summary

Add a /tags page that lists every tag with its image count, sorted by popularity, each linking to the filtered library view. Requires: (1) two new query parameters on the existing /api/v1/tags endpoint to support sort-by-count and zero-count exclusion, (2) query-parameter-driven filtering on the library route so tag browser links deep-link correctly, (3) a new TagBrowserComponent, and (4) a navigation entry point from the library.

Technical Context

Language/Version: Python 3.12 (API), TypeScript strict / Angular 19 (UI) Primary Dependencies: FastAPI, SQLAlchemy 2.x async, Angular standalone components Storage: PostgreSQL (read-only for this feature — no schema changes) Testing: pytest + httpx (API integration), Jasmine/Karma (Angular unit) Target Platform: Web (same stack as all prior features) Project Type: Web service + SPA Performance Goals: Tag list page load perceived as instant (same bar as library) Constraints: No schema changes; no new dependencies; counts must be accurate at page-load time Scale/Scope: Personal library — tag count is bounded; no pagination UI needed for tag browser, but the API call uses existing paginated endpoint

Constitution Check

Principle Status Notes
§2.1 Strict separation of concerns UI calls API; API owns all DB logic
§2.5 Repository layer All query changes go in TagRepository.list_tags()
§2.6 No speculative abstraction No new interfaces; extends existing repo method
§3.1 API versioning /api/v1/ Modifying existing versioned endpoint
§3.2 OpenAPI as contract New query params documented via FastAPI
§3.3 Error shape No new error paths
§3.4 Pagination Existing endpoint already paginates; tag browser fetches with limit=500 (safe upper bound for a personal library)
§4.1 Tags lowercase normalised No change to tag creation/normalisation
§5.1 TDD non-negotiable Tests written before implementation in tasks
§5.3 Tests colocated API tests in api/tests/, Angular spec next to component
§6 Tech stack No new dependencies
§7.3 Linting/formatting enforced ng lint + ruff gates in tasks

Gate: All principles pass. Phase 0 research not required — no unknowns.

Project Structure

Documentation (this feature)

specs/007-tag-browser/
├── plan.md              ← this file
├── research.md          ← not required (no unknowns)
├── data-model.md        ← see below (derived data, no schema changes)
├── contracts/
│   └── tags-endpoint.md ← enhanced GET /api/v1/tags contract
└── tasks.md             ← generated by /speckit-tasks

Source Code Changes

api/
├── app/
│   ├── repositories/
│   │   └── tag_repo.py          ← extend list_tags() with sort + min_count params
│   └── routers/
│       └── tags.py              ← expose sort + min_count as query params
└── tests/
    ├── integration/
    │   └── test_tags.py         ← new tests: sort=count_desc, min_count=1
    └── unit/
        └── test_tags.py         ← unit tests for repo sort/filter logic (if applicable)

ui/src/app/
├── tags/
│   ├── tags.component.ts        ← new TagBrowserComponent
│   └── tags.component.spec.ts   ← component tests
├── services/
│   └── tag.service.ts           ← add sort param to list() method
├── library/
│   └── library.component.ts     ← read ?tags= query param on init; add /tags nav link
└── app.routes.ts                ← add /tags route (lazy-loaded)

Design Decisions

API: extend existing endpoint rather than add new one

The /api/v1/tags endpoint already returns tags with image_count. Two new optional query parameters make it serve the tag browser without breaking existing callers (the library autocomplete uses the endpoint unchanged):

  • sort: name (default, current behaviour) | count_desc (tag browser use case)
  • min_count: integer, default 0 (all tags, current behaviour) | 1 (excludes zero-count tags)

Library: query param deep-linking

The library component currently manages activeFilters in memory only. Adding ?tags=cat,funny query parameter support (read on ngOnInit via ActivatedRoute) allows the tag browser to link directly to a pre-filtered library view. The library already uses addFilter() / applyFilter() internally — reading from query params simply pre-populates activeFilters before the initial load() call. Navigation from within the library that changes filters should update the URL to keep it shareable, but that is a polish concern — minimum requirement is that arriving at /?tags=cat shows the cat-filtered library.

Tag browser UI layout

A responsive chip/card grid sorted by count descending. Each item shows the tag name and count. Each item is a routerLink to /?tags=<name>. Follows the existing design token system (--surface, --accent, --chip styles). Empty state if no tags exist.