# S01 Research — Dedicated Sub-Topic Pages

## Summary

This slice adds dedicated pages at `/topics/:category/:subtopic` that show techniques filtered by sub-topic tag, grouped by creator, with breadcrumb navigation. The work is straightforward — it follows established patterns already in the codebase (CreatorDetail page, techniques list endpoint). No new technology, no risky integration.

## Recommendation

Follow the existing CreatorDetail pattern: new backend endpoint → new API client function → new React page → route registration → update TopicsBrowse links. Breadcrumbs are a new UI pattern but trivially simple (just styled links).

## Implementation Landscape

### What Exists

**Backend — `routers/topics.py`:**
- `GET /topics` — returns full category hierarchy with sub-topic counts from `canonical_tags.yaml`
- `GET /topics/{category_slug}` — returns techniques filtered by `topic_category` (slug→title case normalization). Returns `PaginatedResponse` with `TechniquePageRead` items including `creator_name` and `creator_slug`.
- Missing: no endpoint to filter by **sub-topic tag** (i.e., filter where a specific tag appears in `topic_tags` ARRAY column)

**Backend — `routers/techniques.py`:**
- `GET /techniques` — supports `category` and `creator_slug` filters, but NOT tag-based filtering
- The `topic_tags` column is `ARRAY(String)` on the `TechniquePage` model (line 215 in models.py)

**Frontend — `TopicsBrowse.tsx`:**
- Renders 7 category cards with expandable sub-topic lists
- Sub-topic links currently go to `/search?q={subtopic_name}&scope=topics` — this is what needs to change to `/topics/{category}/{subtopic}`
- Uses `fetchTopics()` API call which returns `TopicCategory[]`

**Frontend — `App.tsx` routes:**
- Has `/topics` route → `TopicsBrowse`
- No `/topics/:category/:subtopic` route exists

**Frontend — `public-client.ts`:**
- Has `fetchTopics()` but no function to fetch techniques by sub-topic
- `TechniqueListItem` and `TechniqueListResponse` types already defined and suitable for the sub-topic page

**Schemas:**
- `PaginatedResponse` is generic (items as `list`, total, offset, limit)
- `TechniquePageRead` includes `creator_name`, `creator_slug` — perfect for grouping by creator in the UI

**Canonical tags (`config/canonical_tags.yaml`):**
- 7 categories with 6-12 sub-topics each
- Sub-topic names are plain strings (e.g., "bass", "compression", "reverb")
- Category names are title-case with spaces (e.g., "Sound Design", "Music Theory")

### What Needs to Be Built

#### Backend: New endpoint `GET /topics/{category_slug}/{subtopic_slug}`

Add to `routers/topics.py`. Filters `TechniquePage` where:
- `topic_category` ilike matches the category slug (existing pattern from `get_topic_techniques`)
- `topic_tags` array contains the sub-topic name (use PostgreSQL `ANY()` or SQLAlchemy `.any()` on the ARRAY column)

Return `PaginatedResponse` with `TechniquePageRead` items, eager-loading creator. Also return the category name and description for breadcrumb context — or let the frontend derive this from the slug (simpler, avoids a new schema).

Slug normalization: `compression` → match against `topic_tags` case-insensitively. Sub-topic names in the YAML are lowercase, so a simple `.lower()` match works.

**Key SQLAlchemy pattern for ARRAY contains:**
```python
from sqlalchemy import any_
# WHERE 'compression' = ANY(topic_tags)
stmt = select(TechniquePage).where(
    func.lower(any_(TechniquePage.topic_tags)) == subtopic_name.lower()
)
# Or using .any() method on the column:
stmt = select(TechniquePage).where(
    TechniquePage.topic_tags.any(subtopic_name, operator=operators.ilike_op)
)
```

The simpler approach: since tags are stored lowercase and slugs will be lowercase, exact match with `.any()` should suffice.

#### Frontend: New page `SubTopicPage.tsx`

Pattern: follow `CreatorDetail.tsx` structure.
- URL params: `category` and `subtopic` from route
- Fetch techniques via new API function
- Group techniques by `creator_name` for display (the roadmap says "techniques grouped by creator")
- Show breadcrumbs: Topics → {Category} → {Sub-topic}
- Handle loading, error, empty states

#### Frontend: Route registration

Add to `App.tsx`:
```tsx
<Route path="/topics/:category/:subtopic" element={<SubTopicPage />} />
```
Must be placed before the `/topics` catch-all or use exact matching.

#### Frontend: Update TopicsBrowse links

Change sub-topic `<Link>` from:
```tsx
to={`/search?q=${encodeURIComponent(st.name)}&scope=topics`}
```
To:
```tsx
to={`/topics/${catSlug(cat.name)}/${st.name.toLowerCase().replace(/\s+/g, '-')}`}
```

#### Frontend: API client function

Add `fetchSubTopicTechniques(category: string, subtopic: string, params?)` to `public-client.ts`.

### Natural Task Decomposition

1. **Backend endpoint** — Add `GET /topics/{category_slug}/{subtopic_slug}` to `routers/topics.py`. Also add a helper endpoint or modify the existing `GET /topics` to return category metadata (name, description) for breadcrumb use. Add integration test.

2. **Frontend page + routing** — Create `SubTopicPage.tsx`, add API client function, register route in `App.tsx`, update `TopicsBrowse.tsx` links. Add breadcrumb component (can be inline, doesn't need its own component file for this simple case).

3. **CSS styling** — Add styles for the sub-topic page layout, breadcrumbs, and creator-grouped technique list. Follow existing card/list patterns.

These could be 2 tasks (backend, frontend) or 3 if CSS is separated. The backend task is a prerequisite since the frontend needs the endpoint.

### Constraints and Edge Cases

- **Empty sub-topics:** Some sub-topics may have 0 techniques. The page should show a friendly empty state, not a blank page.
- **Slug normalization:** Category "Sound Design" → slug "sound-design". Sub-topic "hi-hat" already has a hyphen. URL: `/topics/sound-design/hi-hat`. Need consistent slug↔name conversion.
- **Category breadcrumb link:** The breadcrumb "Topics → Mixing → Compression" — should "Mixing" link somewhere? Currently there's no dedicated category page (only the expanded card on TopicsBrowse). Options: (a) link to `/topics` with the category pre-expanded, (b) link to `/topics#mixing`, (c) don't make it a link. Option (c) is simplest and avoids scope creep.
- **Grouping by creator:** The roadmap says "techniques grouped by creator." This is pure frontend logic — group `TechniquePageRead[]` by `creator_name` and render sections.
- **No sub-topic description exists** in the YAML — only names. The page header will show the sub-topic name and parent category but no description paragraph.

### Verification Strategy

- Backend: Integration test — create technique pages with known tags, hit `GET /topics/{cat}/{subtopic}`, verify filtered results and pagination
- Frontend: Build succeeds with zero TypeScript errors (`npm run build`)
- Browser: Navigate to `/topics/mixing/compression` and verify breadcrumbs, technique list grouped by creator, links work
- TopicsBrowse: Sub-topic links now navigate to `/topics/{cat}/{subtopic}` instead of search

### Skills

No additional skills needed. This is standard React + FastAPI CRUD with established patterns in the codebase.
