---
id: S07
parent: M014
milestone: M014
provides:
  - technique_section search result type with section_anchor and section_heading fields
  - Per-section Qdrant embeddings for v2 technique pages
  - Deep link scroll to any hash fragment on technique pages
requires:
  - slice: S04
    provides: v2 technique pages with body_sections JSONB and body_sections_format field
  - slice: S05
    provides: Frontend section rendering with slugified heading IDs for anchor targets
affects:
  []
key_files:
  - backend/schemas.py
  - backend/pipeline/stages.py
  - backend/pipeline/qdrant_client.py
  - backend/search_service.py
  - backend/pipeline/test_section_embedding.py
  - frontend/src/api/public-client.ts
  - frontend/src/pages/TechniquePage.tsx
  - frontend/src/pages/SearchResults.tsx
  - frontend/src/components/SearchAutocomplete.tsx
key_decisions:
  - Removed Qdrant type_filter for topics scope so technique_section results appear in semantic search
  - Section title field carries page title; section_heading is separate field for frontend display
  - Generalized TechniquePage hash scroll to any fragment (not just #km- prefix)
patterns_established:
  - Per-section embedding pattern: iterate body_sections JSON, build composite embed text with parent context (creator + page title + section heading + content), deterministic UUID from page_id:section_slug
  - Stale point cleanup pattern: delete_sections_by_page_id() before upsert to handle heading renames without orphan points
observability_surfaces:
  - Stage 6 logs section point count per page during embedding
drill_down_paths:
  - .gsd/milestones/M014/slices/S07/tasks/T01-SUMMARY.md
  - .gsd/milestones/M014/slices/S07/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-04-03T02:16:37.295Z
blocker_discovered: false
---

# S07: Search — Per-Section Embeddings + Deep Linking

**Added per-section Qdrant embeddings for v2 technique pages and section-level search results with deep links that scroll to the target section.**

## What Happened

Two tasks delivered section-level search end-to-end.

**T01 (Backend)** added the full embedding and search pipeline for v2 technique page sections. `_slugify_heading()` produces anchors matching the frontend's `slugify()`. `QdrantManager` gained `upsert_technique_sections()` with deterministic UUIDs (`uuid5` keyed on `page_id:section_slug`) and `delete_sections_by_page_id()` for stale point cleanup before re-indexing. Stage 6 now iterates v2 pages, builds section-level embed text including subsection content, and upserts to Qdrant with `technique_section` type payloads. `SearchService._enrich_qdrant_results()` maps technique_section payloads to `SearchResultItem` with `section_anchor` and `section_heading` fields. The Qdrant type_filter for topics scope was removed so section results appear in semantic search.

All failure modes are non-blocking — Qdrant errors, embedding API failures, and malformed body_sections are logged and skipped without failing the pipeline. v1 pages produce zero section points. 22 unit tests cover slugify, deterministic UUIDs, QdrantManager methods, stage 6 logic, and negative cases.

**T02 (Frontend)** added `section_anchor` and `section_heading` to the TypeScript `SearchResultItem` type. Generalized TechniquePage's hash scroll from `#km-` prefixed hashes to any fragment — now handles both key moment and section anchors. Added `technique_section` routing in `SearchResults.tsx` and `SearchAutocomplete.tsx` with "Section" badge display. Also fixed a pre-existing bug where all autocomplete result links pointed to `/techniques/${item.slug}` regardless of type — key_moment and technique_section results now link correctly with hash fragments.

Frontend builds with zero TypeScript errors.

## Verification

All slice-level verification checks pass:
1. `PYTHONPATH=backend python -m pytest backend/pipeline/test_section_embedding.py -v` — 22 tests pass (slugify, UUIDs, Qdrant methods, stage 6 logic, negative cases)
2. `PYTHONPATH=backend python -c "from pipeline.stages import _slugify_heading; assert _slugify_heading('Grain Position Control') == 'grain-position-control'"` — slugify OK
3. `grep -q 'section_anchor' backend/schemas.py` — present
4. `grep -q 'technique_section' backend/search_service.py` — present
5. `cd frontend && npm run build` — 57 modules, zero errors, built in 906ms

## Requirements Advanced

- R009 — Qdrant now indexes per-section embeddings for v2 technique pages alongside existing page-level and key moment embeddings
- R005 — Search results now include section-level matches with deep links that scroll to the target section

## Requirements Validated

None.

## New Requirements Surfaced

None.

## Requirements Invalidated or Re-scoped

None.

## Deviations

Corrected slugify expectation: 'LFO Routing & Modulation' produces 'lfo-routing-modulation' (single hyphen), not 'lfo-routing---modulation' as the plan speculated. Removed Qdrant type_filter for topics scope to include technique_section in semantic search results. Fixed pre-existing autocomplete link bug for key_moment type as part of T02.

## Known Limitations

None.

## Follow-ups

None.

## Files Created/Modified

- `backend/schemas.py` — Added section_anchor and section_heading optional fields to SearchResultItem
- `backend/pipeline/stages.py` — Added _slugify_heading() helper and v2 section embedding block in stage 6
- `backend/pipeline/qdrant_client.py` — Added upsert_technique_sections() and delete_sections_by_page_id() to QdrantManager
- `backend/search_service.py` — Added technique_section branch to _enrich_qdrant_results(), removed type_filter for topics scope
- `backend/pipeline/test_section_embedding.py` — New: 22 unit tests for slugify, UUIDs, Qdrant section methods, stage 6 logic, negative cases
- `frontend/src/api/public-client.ts` — Added section_anchor and section_heading to SearchResultItem type
- `frontend/src/pages/TechniquePage.tsx` — Generalized hash scroll from #km- only to any fragment
- `frontend/src/pages/SearchResults.tsx` — Added technique_section link routing, Section badge, partial match filtering
- `frontend/src/components/SearchAutocomplete.tsx` — Added technique_section type label and section-aware link routing, fixed key_moment links
