feat: Key moment search results now link to parent technique page with…
- "frontend/src/api/public-client.ts" - "frontend/src/pages/SearchResults.tsx" - "frontend/src/pages/TechniquePage.tsx" GSD-Task: S01/T02
This commit is contained in:
parent
af250a6f5d
commit
6d4390414a
7 changed files with 123 additions and 4 deletions
|
|
@ -19,7 +19,7 @@
|
||||||
- Estimate: 45m
|
- Estimate: 45m
|
||||||
- Files: backend/pipeline/stages.py, backend/pipeline/qdrant_client.py, backend/search_service.py, backend/schemas.py, backend/tests/test_search.py
|
- Files: backend/pipeline/stages.py, backend/pipeline/qdrant_client.py, backend/search_service.py, backend/schemas.py, backend/tests/test_search.py
|
||||||
- Verify: cd /home/aux/projects/content-to-kb-automator && python -m pytest backend/tests/test_search.py -v 2>&1 | tail -30
|
- Verify: cd /home/aux/projects/content-to-kb-automator && python -m pytest backend/tests/test_search.py -v 2>&1 | tail -30
|
||||||
- [ ] **T02: Fix frontend search result links and add hash-scroll to technique page** — Fix the frontend so clicking a key moment search result navigates to the parent technique page and scrolls to the specific moment.
|
- [x] **T02: Key moment search results now link to parent technique page with hash-scroll to the specific moment** — Fix the frontend so clicking a key moment search result navigates to the parent technique page and scrolls to the specific moment.
|
||||||
|
|
||||||
1. **TypeScript interface** (`frontend/src/api/public-client.ts`): Add `technique_page_slug?: string` to the `SearchResultItem` interface (line ~10).
|
1. **TypeScript interface** (`frontend/src/api/public-client.ts`): Add `technique_page_slug?: string` to the `SearchResultItem` interface (line ~10).
|
||||||
|
|
||||||
|
|
|
||||||
16
.gsd/milestones/M008/slices/S01/tasks/T01-VERIFY.json
Normal file
16
.gsd/milestones/M008/slices/S01/tasks/T01-VERIFY.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T01",
|
||||||
|
"unitId": "M008/S01/T01",
|
||||||
|
"timestamp": 1774933368313,
|
||||||
|
"passed": true,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd /home/aux/projects/content-to-kb-automator",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4,
|
||||||
|
"verdict": "pass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
79
.gsd/milestones/M008/slices/S01/tasks/T02-SUMMARY.md
Normal file
79
.gsd/milestones/M008/slices/S01/tasks/T02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S01
|
||||||
|
milestone: M008
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/api/public-client.ts", "frontend/src/pages/SearchResults.tsx", "frontend/src/pages/TechniquePage.tsx"]
|
||||||
|
key_decisions: ["Key moment hash anchor uses km.id (UUID) for uniqueness", "Fallback for missing technique_page_slug re-searches by title instead of 404ing"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "npm run build passes with zero TypeScript errors, producing a clean production bundle."
|
||||||
|
completed_at: 2026-03-31T05:03:59.917Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Key moment search results now link to parent technique page with hash-scroll to the specific moment
|
||||||
|
|
||||||
|
> Key moment search results now link to parent technique page with hash-scroll to the specific moment
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S01
|
||||||
|
milestone: M008
|
||||||
|
key_files:
|
||||||
|
- frontend/src/api/public-client.ts
|
||||||
|
- frontend/src/pages/SearchResults.tsx
|
||||||
|
- frontend/src/pages/TechniquePage.tsx
|
||||||
|
key_decisions:
|
||||||
|
- Key moment hash anchor uses km.id (UUID) for uniqueness
|
||||||
|
- Fallback for missing technique_page_slug re-searches by title instead of 404ing
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-31T05:03:59.917Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Key moment search results now link to parent technique page with hash-scroll to the specific moment
|
||||||
|
|
||||||
|
**Key moment search results now link to parent technique page with hash-scroll to the specific moment**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Added technique_page_slug to the frontend SearchResultItem interface, created routing logic that sends key moment clicks to /techniques/{parent_slug}#km-{id}, added anchor IDs to key moment list items in TechniquePage, and added a useEffect that scrolls to the hash target after data loads.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
npm run build passes with zero TypeScript errors, producing a clean production bundle.
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 3500ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/api/public-client.ts`
|
||||||
|
- `frontend/src/pages/SearchResults.tsx`
|
||||||
|
- `frontend/src/pages/TechniquePage.tsx`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
None.
|
||||||
|
|
@ -17,6 +17,7 @@ export interface SearchResultItem {
|
||||||
creator_slug: string;
|
creator_slug: string;
|
||||||
topic_category: string;
|
topic_category: string;
|
||||||
topic_tags: string[];
|
topic_tags: string[];
|
||||||
|
technique_page_slug?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResponse {
|
export interface SearchResponse {
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,21 @@ export default function SearchResults() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSearchResultLink(item: SearchResultItem): string {
|
||||||
|
if (item.type === "key_moment") {
|
||||||
|
if (item.technique_page_slug) {
|
||||||
|
return `/techniques/${item.technique_page_slug}#km-${item.slug || item.title}`;
|
||||||
|
}
|
||||||
|
// Graceful fallback — re-search instead of 404
|
||||||
|
return `/search?q=${encodeURIComponent(item.title)}`;
|
||||||
|
}
|
||||||
|
return `/techniques/${item.slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
function SearchResultCard({ item }: { item: SearchResultItem }) {
|
function SearchResultCard({ item }: { item: SearchResultItem }) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/techniques/${item.slug}`}
|
to={getSearchResultLink(item)}
|
||||||
className="search-result-card"
|
className="search-result-card"
|
||||||
>
|
>
|
||||||
<div className="search-result-card__header">
|
<div className="search-result-card__header">
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,18 @@ export default function TechniquePage() {
|
||||||
};
|
};
|
||||||
}, [slug, selectedVersion]);
|
}, [slug, selectedVersion]);
|
||||||
|
|
||||||
|
// Scroll to key moment if URL has a #km- hash fragment
|
||||||
|
useEffect(() => {
|
||||||
|
if (!technique) return;
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (hash.startsWith("#km-")) {
|
||||||
|
const el = document.getElementById(hash.slice(1));
|
||||||
|
if (el) {
|
||||||
|
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [technique]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="loading">Loading technique…</div>;
|
return <div className="loading">Loading technique…</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +430,7 @@ export default function TechniquePage() {
|
||||||
<h2>Key Moments</h2>
|
<h2>Key Moments</h2>
|
||||||
<ol className="technique-moments__list">
|
<ol className="technique-moments__list">
|
||||||
{technique.key_moments.map((km) => (
|
{technique.key_moments.map((km) => (
|
||||||
<li key={km.id} className="technique-moment">
|
<li key={km.id} id={`km-${km.id}`} className="technique-moment">
|
||||||
<h3 className="technique-moment__title">{km.title}</h3>
|
<h3 className="technique-moment__title">{km.title}</h3>
|
||||||
<div className="technique-moment__meta">
|
<div className="technique-moment__meta">
|
||||||
{km.video_filename && (
|
{km.video_filename && (
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/ModeToggle.tsx","./src/components/ReportIssueModal.tsx","./src/components/StatusBadge.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/MomentDetail.tsx","./src/pages/ReviewQueue.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
|
||||||
Loading…
Add table
Reference in a new issue