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' : ''}
+
+ )}
))}