# S01 Research — Fix Key Moment Search Links

## Summary

Two distinct bugs cause search result links to break. Both are straightforward to fix with known patterns already in the codebase. No new technology or libraries needed.

**Bug 1 — Key moment results link to nonexistent routes:** `SearchResultCard` in `SearchResults.tsx` links all results to `/techniques/${item.slug}`. For key moments, the backend returns an empty string for `slug` (keyword path) or a synthesized slug from the moment title (Qdrant path). Either way, clicking navigates to a technique page that doesn't exist → catch-all redirect to `/`.

**Bug 2 — Qdrant key moment payload has no parent technique page reference:** The Qdrant payload for key moments (see `qdrant_client.py:172-185`) stores `moment_id`, `source_video_id`, `title`, timestamps, and `content_type` — but NOT `technique_page_id` or the parent technique page slug. There's no way to resolve a key moment to its parent page from Qdrant data alone.

**Bug 3 (minor) — Qdrant technique page payload missing slug:** The `_enrich_results` method in `search_service.py` falls back to `title.lower().replace(" ", "-")` for slug, since Qdrant payloads don't include it. DB slugs are LLM-generated in stage 5 and won't match this naive transform.

## Recommendation

### Backend changes

1. **Add `technique_page_slug` to Qdrant key moment payloads** in `qdrant_client.py` `upsert_key_moments()`. The stage 6 caller (`stages.py:~1005`) already has access to the technique page data. Add `technique_page_slug` and `technique_page_id` to the moment dict.

2. **Add `slug` to Qdrant technique page payloads** in `qdrant_client.py` `upsert_technique_pages()`. The caller already has the slug in the `page` dict — just pass it through.

3. **Fix `_enrich_results` in `search_service.py`** to use the Qdrant payload slug for technique pages (now available) and to add `technique_page_slug` to key moment results.

4. **Fix `keyword_search` in `search_service.py`** for key moments — currently sets `"slug": ""`. Join `TechniquePage` through `KeyMoment.technique_page_id` to get the parent slug.

5. **Add `technique_page_slug` field to `SearchResultItem` schema** in `schemas.py` and `SearchResultItem` interface in `public-client.ts`.

6. **Re-index existing Qdrant data** — existing points lack the new payload fields. Either re-run stage 6 for all videos, or add a migration script. Re-running stage 6 via the admin pipeline trigger is the simplest path.

### Frontend changes

7. **Fix `SearchResultCard` link logic in `SearchResults.tsx`:** For `type === "key_moment"`, link to `/techniques/${item.technique_page_slug}#km-${item.slug || item.title}` (anchor to the moment on the parent technique page). For `type === "technique_page"`, keep linking to `/techniques/${item.slug}`.

8. **Add `id` attributes to key moment elements in `TechniquePage.tsx`:** Currently `<li key={km.id}>` but no `id` attribute for anchor targeting. Add `id={`km-${km.id}`}` to enable hash-based scrolling.

9. **Add scroll-to-hash logic in `TechniquePage.tsx`:** After technique data loads, if `window.location.hash` matches a key moment ID, scroll to it. Use `useEffect` with a ref or `document.getElementById`.

## Implementation Landscape

### Files to modify

| File | Change | Risk |
|------|--------|------|
| `backend/pipeline/qdrant_client.py` | Add `slug` to technique page payloads; add `technique_page_slug`/`technique_page_id` to key moment payloads | Low — additive payload change |
| `backend/pipeline/stages.py` (~line 1005) | Pass slug and technique_page_slug in dicts sent to Qdrant upsert | Low — data already available in scope |
| `backend/search_service.py` | Fix `_enrich_results` to use payload slug; fix `keyword_search` to join technique page for key moments; add `technique_page_slug` to result dicts | Medium — query changes |
| `backend/schemas.py` | Add `technique_page_slug` field to `SearchResultItem` | Low |
| `frontend/src/api/public-client.ts` | Add `technique_page_slug` to `SearchResultItem` interface | Low |
| `frontend/src/pages/SearchResults.tsx` | Fix `SearchResultCard` link for key moments | Low |
| `frontend/src/pages/TechniquePage.tsx` | Add `id` attrs to moment elements; add scroll-to-hash on load | Low |
| `backend/tests/test_search.py` | Update keyword fallback test for key moment slug | Low |

### Natural task boundaries

**T01 — Backend: Qdrant payload + search service fixes** (est: 45min)
- Add slug to Qdrant technique page payload
- Add technique_page_slug to Qdrant key moment payload
- Fix `_enrich_results` to use payload slug
- Fix `keyword_search` to join TechniquePage for key moment results
- Add `technique_page_slug` to `SearchResultItem` schema
- Update/add tests in `test_search.py`

**T02 — Frontend: Search result links + hash scroll** (est: 30min)
- Add `technique_page_slug` to frontend `SearchResultItem` type
- Fix `SearchResultCard` link logic based on result type
- Add `id` attributes to key moment `<li>` elements in `TechniquePage.tsx`
- Add `useEffect` scroll-to-hash after data loads
- Verify: search → click key moment → lands on technique page at correct section

**T03 — Re-index Qdrant data** (est: 15min)
- Trigger stage 6 re-run for all processed videos via admin API
- Verify Qdrant payloads now include slug fields
- Note: This is operational, not code — could be folded into T01 verification if preferred

### Key constraints

- `KeyMoment.technique_page_id` is nullable — some moments may not be linked to a technique page yet. The search result card needs a fallback (e.g., link to creator page, or show disabled with "unlinked" badge).
- Qdrant re-indexing is required for the fix to take full effect on semantic search results. Keyword search fix works immediately.
- The `SearchResultCard` component handles all result types — changes must not break technique_page or creator results.

### Verification strategy

The roadmap demo says: "Search 'compression', click any key moment result → lands on parent technique page with key moment visible." Verification:
1. Run backend tests (`pytest backend/tests/test_search.py`)
2. Build frontend (`cd frontend && npm run build`)
3. On ub01: rebuild and deploy, trigger re-index
4. Browser verify: search for a term → click a key moment result → confirm it lands on the correct technique page with the moment scrolled into view
