diff --git a/backend/routers/techniques.py b/backend/routers/techniques.py index c4a761f..6f0944b 100644 --- a/backend/routers/techniques.py +++ b/backend/routers/techniques.py @@ -38,34 +38,53 @@ async def list_techniques( db: AsyncSession = Depends(get_session), ) -> PaginatedResponse: """List technique pages with optional category/creator filtering.""" - stmt = select(TechniquePage) + # Correlated subquery for key moment count (same pattern as creators.py) + key_moment_count_sq = ( + select(func.count()) + .where(KeyMoment.technique_page_id == TechniquePage.id) + .correlate(TechniquePage) + .scalar_subquery() + ) + # Build base query with filters + base_stmt = select(TechniquePage.id) if category: - stmt = stmt.where(TechniquePage.topic_category == category) - + base_stmt = base_stmt.where(TechniquePage.topic_category == category) if creator_slug: - # Join to Creator to filter by slug - stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id).where( + base_stmt = base_stmt.join(Creator, TechniquePage.creator_id == Creator.id).where( Creator.slug == creator_slug ) # Count total before pagination - from sqlalchemy import func - - count_stmt = select(func.count()).select_from(stmt.subquery()) + count_stmt = select(func.count()).select_from(base_stmt.subquery()) count_result = await db.execute(count_stmt) total = count_result.scalar() or 0 + # Main query with subquery column + stmt = select( + TechniquePage, + key_moment_count_sq.label("key_moment_count"), + ) + if category: + stmt = stmt.where(TechniquePage.topic_category == category) + if creator_slug: + stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id).where( + Creator.slug == creator_slug + ) + stmt = stmt.options(selectinload(TechniquePage.creator)).order_by(TechniquePage.created_at.desc()).offset(offset).limit(limit) result = await db.execute(stmt) - pages = result.scalars().all() + rows = result.all() items = [] - for p in pages: + for row in rows: + p = row[0] + km_count = row[1] or 0 item = TechniquePageRead.model_validate(p) if p.creator: item.creator_name = p.creator.name item.creator_slug = p.creator.slug + item.key_moment_count = km_count items.append(item) return PaginatedResponse( diff --git a/backend/schemas.py b/backend/schemas.py index 50de0aa..c46b417 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -138,6 +138,7 @@ class TechniquePageRead(TechniquePageBase): creator_slug: str = "" source_quality: str | None = None view_count: int = 0 + key_moment_count: int = 0 created_at: datetime updated_at: datetime diff --git a/frontend/src/App.css b/frontend/src/App.css index 2ff3c25..8b9a806 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1110,6 +1110,17 @@ a.app-footer__repo:hover { line-height: 1.4; } +.recent-card__moments { + font-size: 0.75rem; + color: var(--color-text-tertiary); + white-space: nowrap; +} + +.pill--tag { + font-size: 0.625rem; + padding: 0 0.375rem; +} + /* ── Search results page ──────────────────────────────────────────────────── */ .search-results-page { diff --git a/frontend/src/api/public-client.ts b/frontend/src/api/public-client.ts index 5fd03b7..254ea2a 100644 --- a/frontend/src/api/public-client.ts +++ b/frontend/src/api/public-client.ts @@ -102,6 +102,7 @@ export interface TechniqueListItem { creator_slug: string; source_quality: string | null; view_count: number; + key_moment_count: number; created_at: string; updated_at: string; } diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index ff188e5..c89d91f 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -210,6 +210,9 @@ export default function Home() { {t.topic_category} + {t.topic_tags && t.topic_tags.length > 0 && t.topic_tags.map(tag => ( + {tag} + ))} {t.summary && ( {t.summary.length > 100 @@ -217,6 +220,11 @@ export default function Home() { : t.summary} )} + {t.key_moment_count > 0 && ( + + {t.key_moment_count} moment{t.key_moment_count !== 1 ? 's' : ''} + + )} ))}