feat: Topics page loads with all categories collapsed; expand/collapse…

- "frontend/src/pages/TopicsBrowse.tsx"
- "frontend/src/App.css"

GSD-Task: S02/T01
This commit is contained in:
jlightner 2026-03-31 08:30:55 +00:00
parent 717f6c0785
commit 41bf06e431
12 changed files with 720 additions and 6 deletions

View file

@ -6,7 +6,7 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add
## Slice Overview ## Slice Overview
| ID | Slice | Risk | Depends | Done | After this | | ID | Slice | Risk | Depends | Done | After this |
|----|-------|------|---------|------|------------| |----|-------|------|---------|------|------------|
| S01 | Interaction Delight & Discovery | medium | — | | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. | | S01 | Interaction Delight & Discovery | medium | — | | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. |
| S02 | Topics, Creator Stats & Card Polish | medium | — | ⬜ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. | | S02 | Topics, Creator Stats & Card Polish | medium | — | ⬜ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. |
| S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. | | S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. |
| S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. | | S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. |

View file

@ -0,0 +1,106 @@
---
id: S01
parent: M011
milestone: M011
provides:
- Card hover animation pattern (scale+shadow) on all card types
- Stagger entrance animation utility class
- GET /api/v1/techniques/random endpoint
- Random Technique button on homepage
requires:
[]
affects:
[]
key_files:
- frontend/src/App.css
- frontend/src/pages/Home.tsx
- frontend/src/pages/TopicsBrowse.tsx
- frontend/src/pages/CreatorDetail.tsx
- frontend/src/pages/SubTopicPage.tsx
- frontend/src/pages/SearchResults.tsx
- backend/routers/techniques.py
- frontend/src/api/public-client.ts
key_decisions:
- CSS stagger pattern uses --stagger-index custom property with calc() delay rather than nth-child selectors — works with dynamic lists
- Dedicated /random endpoint returning just {slug} rather than reusing sort=random on list endpoint — cleaner API surface
- /random route placed before /{slug} to avoid FastAPI slug capture
patterns_established:
- card-stagger utility class: add className='card-stagger' and style={{ '--stagger-index': i }} to any .map() loop for entrance animations
- Featured section visual treatment: gradient border-image + double box-shadow glow
observability_surfaces:
- none
drill_down_paths:
- .gsd/milestones/M011/slices/S01/tasks/T01-SUMMARY.md
- .gsd/milestones/M011/slices/S01/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-03-31T08:25:58.040Z
blocker_discovered: false
---
# S01: Interaction Delight & Discovery
**Added card hover animations (scale+shadow) on all 6 card types, staggered entrance animations across 5 page components, gradient-border glow on featured technique, and a Random Technique button with backend endpoint.**
## What Happened
Two tasks delivered CSS interaction polish and a random discovery feature.
T01 added `transform: scale(1.02)` hover transitions with `will-change: transform` to all 6 card types (recent-card, creator-technique-card, subtopic-technique-card, search-result-card, topic-card, nav-card). Created a `@keyframes cardEnter` animation (opacity 0→1, translateY 12px→0, 300ms ease-out) with a `.card-stagger` utility class driven by a `--stagger-index` CSS custom property. Applied stagger indices via JSX style props across Home.tsx, TopicsBrowse.tsx, CreatorDetail.tsx, SubTopicPage.tsx, and SearchResults.tsx. The featured technique section got a gradient `border-image` and double box-shadow glow treatment.
T02 added a `GET /api/v1/techniques/random` backend endpoint (placed before the `/{slug}` route to avoid capture) that returns `{slug}` via `ORDER BY random() LIMIT 1`, with 404 when no techniques exist. Frontend gets `fetchRandomTechnique()` in the API client and a 🎲 Random Technique button on the homepage with loading and error states.
Both tasks verified with TypeScript (`tsc --noEmit`) and Vite production build (50 modules). All grep checks confirm features landed in expected files.
## Verification
All slice-level checks pass:
- `npx tsc --noEmit` — exit 0, no type errors
- `npm run build` — exit 0, 50 modules, 806ms build
- `grep cardEnter App.css` — found
- `grep card-stagger App.css` — found
- `grep fetchRandomTechnique public-client.ts` — found
- `grep /random techniques.py` — found
- `grep Random Home.tsx` — found
- `grep stagger-index` across all 5 page components — found in each (Home:3, TopicsBrowse:1, CreatorDetail:1, SubTopicPage:1, SearchResults:1)
## Requirements Advanced
- R016 — All 6 card types have scale(1.02) hover with smooth 200ms transition. 5 page components use staggered cardEnter animation.
- R017 — Featured technique section has gradient border-image and double box-shadow glow, visually distinct from regular cards.
- R018 — Random Technique button on homepage calls GET /random endpoint and navigates to result. Loading and error states implemented.
## Requirements Validated
None.
## New Requirements Surfaced
None.
## Requirements Invalidated or Re-scoped
None.
## Deviations
SearchResultCard needed a staggerIndex prop threaded through (not anticipated in plan). topic-card had no existing :hover rule — one was added. border-image removes border-radius on the featured card (CSS limitation) but the glow box-shadow still provides the visual treatment.
## Known Limitations
border-image CSS property strips border-radius on the featured technique card. The glow effect (box-shadow) still provides visual distinction but the card corners are square rather than rounded.
## Follow-ups
None.
## Files Created/Modified
- `frontend/src/App.css` — Added card hover scale(1.02) transitions, @keyframes cardEnter, .card-stagger utility, .home-featured glow treatment, .btn--random and .home-random styles
- `frontend/src/pages/Home.tsx` — Added stagger indices to nav-cards and recent cards, Random Technique button with loading/error states
- `frontend/src/pages/TopicsBrowse.tsx` — Added card-stagger class and stagger-index to topic cards
- `frontend/src/pages/CreatorDetail.tsx` — Added card-stagger class and stagger-index to creator technique cards
- `frontend/src/pages/SubTopicPage.tsx` — Added card-stagger class and stagger-index to subtopic technique cards
- `frontend/src/pages/SearchResults.tsx` — Added card-stagger class and stagger-index to search result cards, threaded staggerIndex prop
- `backend/routers/techniques.py` — Added GET /random endpoint before /{slug} route
- `frontend/src/api/public-client.ts` — Added fetchRandomTechnique() API client function

View file

@ -0,0 +1,77 @@
# S01: Interaction Delight & Discovery — UAT
**Milestone:** M011
**Written:** 2026-03-31T08:25:58.041Z
# S01 UAT: Interaction Delight & Discovery
## Preconditions
- Chrysopedia running at http://ub01:8096
- At least 1 technique page exists in the database
- Modern browser (Chrome/Firefox/Safari)
## Test Cases
### TC-01: Card Hover Animation — Homepage Recent Cards
1. Navigate to http://ub01:8096
2. Hover over any card in the "Recently Added" section
3. **Expected:** Card smoothly scales up (~2%) with enhanced shadow over ~200ms
4. Move mouse away from card
5. **Expected:** Card smoothly returns to original size
### TC-02: Card Hover Animation — All Card Types
1. Navigate to Topics page → hover a topic card
2. Navigate to any topic → hover a subtopic technique card
3. Navigate to Creators page → click a creator → hover a creator technique card
4. Use search → hover a search result card
5. On homepage → hover a nav card (Topics/Creators)
6. **Expected:** All 5 card types exhibit the same scale+shadow hover effect
### TC-03: Staggered Entrance Animation — Homepage
1. Hard refresh http://ub01:8096 (Ctrl+Shift+R)
2. Observe the nav cards and recently added cards
3. **Expected:** Cards fade in and slide up sequentially (not all at once). Each card appears ~60ms after the previous one.
### TC-04: Staggered Entrance — Other Pages
1. Navigate to Topics page — cards should stagger in
2. Navigate to a Creator detail page — technique cards should stagger in
3. Navigate to a SubTopic page — technique cards should stagger in
4. Perform a search — result cards should stagger in
5. **Expected:** All pages show sequential card entrance, not simultaneous
### TC-05: Featured Technique Glow
1. Navigate to homepage
2. Scroll to the "Featured Technique" section
3. **Expected:** The featured technique card has a visible gradient border and soft glow (cyan-tinted box shadow) that distinguishes it from regular cards
4. **Note:** Corners may be square due to border-image CSS limitation — this is known
### TC-06: Random Technique Button — Happy Path
1. Navigate to homepage
2. Locate the 🎲 "Random Technique" button (between nav cards and featured section)
3. Click the button
4. **Expected:** Button shows brief loading state, then navigates to a technique page
5. Use browser back, click the button again
6. **Expected:** May navigate to a different technique (randomized server-side)
### TC-07: Random Technique Button — Loading State
1. On homepage, open browser DevTools Network tab
2. Throttle network to "Slow 3G"
3. Click the Random Technique button
4. **Expected:** Button shows a loading indicator while the request is in flight
5. Remove throttle
### TC-08: Random Technique API — Direct
1. Open http://ub01:8096/api/v1/techniques/random in browser or curl
2. **Expected:** JSON response `{"slug": "some-technique-slug"}` with 200 status
3. Refresh multiple times
4. **Expected:** Different slugs returned (assuming multiple techniques exist)
### Edge Cases
### TC-09: Card Stagger with Few Items
1. If a creator has only 1 technique, navigate to their detail page
2. **Expected:** Single card still animates in (no delay needed for index 0)
### TC-10: Featured Technique Glow in Light/Contrast
1. On homepage, inspect the featured technique section
2. **Expected:** Glow is subtle — visible but not overpowering (cyan tint, not neon)

View file

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"taskId": "T02",
"unitId": "M011/S01/T02",
"timestamp": 1774945478573,
"passed": false,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 5,
"verdict": "pass"
},
{
"command": "npx tsc --noEmit",
"exitCode": 1,
"durationMs": 720,
"verdict": "fail"
},
{
"command": "npm run build",
"exitCode": 254,
"durationMs": 96,
"verdict": "fail"
},
{
"command": "grep -q 'fetchRandomTechnique' src/api/public-client.ts",
"exitCode": 2,
"durationMs": 7,
"verdict": "fail"
},
{
"command": "grep -q '/random' ../backend/routers/techniques.py",
"exitCode": 2,
"durationMs": 6,
"verdict": "fail"
}
],
"retryAttempt": 1,
"maxRetries": 2
}

View file

@ -1,6 +1,125 @@
# S02: Topics, Creator Stats & Card Polish # S02: Topics, Creator Stats & Card Polish
**Goal:** Improve information density and visual clarity on Topics and Creator pages **Goal:** Topics page loads collapsed with smooth expand/collapse animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N overflow. Empty subtopics show "Coming soon" badge.
**Demo:** After this: Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. **Demo:** After this: Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon.
## Tasks ## Tasks
- [x] **T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render** — Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions.
## Steps
1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed.
2. Refactor the subtopics rendering from conditional `{isExpanded && (<div className="topic-subtopics">...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique:
- Wrap the subtopics in a `<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;`
- When `data-expanded="true"`, set `grid-template-rows: 1fr`
- The inner `<div className="topic-subtopics">` gets `overflow: hidden; min-height: 0;`
- Always render both wrapper and inner div (remove the conditional render)
3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318):
- `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }`
- `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }`
- `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule)
4. Verify: `cd frontend && npx tsc --noEmit && npm run build`
- Estimate: 30m
- Files: frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css
- Verify: cd frontend && npx tsc --noEmit && npm run build
- [ ] **T02: Creator stats topic-colored pill badges** — Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes.
## Steps
1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";`
2. Find the stats section (~line 108-126) that renders `<span className="creator-detail__topic-stat">` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills:
```tsx
.map(([cat, count]) => (
<span key={cat} className={`badge badge--cat-${catSlug(cat)}`}>
{cat}: {count}
</span>
))
```
Remove the dot separator spans (`queue-card__separator`) between them.
3. Wrap the topic stat pills in a flex container: `<span className="creator-detail__topic-pills">` and add CSS in `frontend/src/App.css`:
```css
.creator-detail__topic-pills {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
align-items: center;
}
```
4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm).
5. Verify: `cd frontend && npx tsc --noEmit && npm run build`
- Estimate: 20m
- Files: frontend/src/pages/CreatorDetail.tsx, frontend/src/App.css
- Verify: cd frontend && npx tsc --noEmit && npm run build
- [ ] **T03: TagList component, tag overflow limit, and empty subtopic handling** — Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
## Steps
1. Create `frontend/src/components/TagList.tsx`:
```tsx
interface TagListProps {
tags: string[];
max?: number;
}
export default function TagList({ tags, max = 4 }: TagListProps) {
const visible = tags.slice(0, max);
const overflow = tags.length - max;
return (
<>
{visible.map(tag => <span key={tag} className="pill">{tag}</span>)}
{overflow > 0 && <span className="pill pill--overflow">+{overflow} more</span>}
</>
);
}
```
2. Add `.pill--overflow` CSS in `frontend/src/App.css`:
```css
.pill--overflow {
background: var(--color-surface-2);
color: var(--color-text-secondary);
font-style: italic;
}
```
3. Replace tag rendering in all 5 sites with `<TagList tags={...} />`:
- `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `<TagList tags={featured.topic_tags} />`
- `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
- `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `<TagList tags={item.topic_tags} />`
- `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
- `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => <span className="pill">{tag}</span>)` with `<TagList tags={...} />`.
4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`:
- Render as a `<span>` (not `<Link>`) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link
- Add a small "Coming soon" badge: `<span className="pill pill--coming-soon">Coming soon</span>`
- Keep the subtopic name visible so users know the topic exists
Use a conditional: `st.technique_count === 0 ? <span className="topic-subtopic topic-subtopic--empty">...</span> : <Link ...>...</Link>`
5. Add CSS for empty subtopic in `frontend/src/App.css`:
```css
.topic-subtopic--empty {
opacity: 0.5;
cursor: default;
}
.topic-subtopic--empty:hover {
background: transparent;
}
.pill--coming-soon {
font-size: 0.65rem;
background: var(--color-surface-2);
color: var(--color-text-secondary);
font-style: italic;
}
```
6. Verify: `cd frontend && npx tsc --noEmit && npm run build`
- Estimate: 40m
- Files: frontend/src/components/TagList.tsx, frontend/src/pages/Home.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/CreatorDetail.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css
- Verify: cd frontend && npx tsc --noEmit && npm run build

View file

@ -0,0 +1,96 @@
# S02 Research: Topics, Creator Stats & Card Polish
## Summary
Straightforward frontend-only changes across 56 files. All patterns (CSS color system, catSlug utility, badge classes, expand/collapse state) already exist. No new dependencies, no backend changes, no unfamiliar technology.
## Targeted Requirements
| Req | Description | Status |
|-----|-------------|--------|
| R019 | Topics page default-collapsed with expand animation | Active — this slice |
| R026 | Creator stats topic-colored pills | Active — this slice |
| R027 | Tag overflow limit (4 + "+N more") | Active — this slice |
| R028 | Empty subtopic handling | Active — this slice |
## Implementation Landscape
### 1. Topics Default-Collapsed (R019)
**File:** `frontend/src/pages/TopicsBrowse.tsx`
- **Current:** Line ~42: `setExpanded(new Set(data.map((c) => c.name)))` — starts all-expanded.
- **Change:** `setExpanded(new Set())` — starts all-collapsed.
- **Animation:** The subtopics container (`div.topic-subtopics`) currently renders conditionally (`{isExpanded && ...}`). For smooth expand/collapse, replace the conditional render with CSS `max-height` + `overflow: hidden` + `transition`. Use a wrapper with `max-height: 0` when collapsed and `max-height: <computed>px` when expanded, or use CSS `grid-template-rows: 0fr / 1fr` technique (better, no fixed height needed).
- **CSS file:** `frontend/src/App.css``.topic-subtopics` starts at line 2318. Add transition properties.
**Recommendation:** Use the CSS `display: grid; grid-template-rows: 0fr → 1fr` pattern for the smoothest transition without needing to know content height. Wrap subtopics in a grid container that transitions `grid-template-rows`.
### 2. Creator Stats Colored Pills (R026)
**File:** `frontend/src/pages/CreatorDetail.tsx`
- **Current:** Lines ~97110: Stats render as `<span className="creator-detail__topic-stat">Cat: N</span>` with dot separators.
- **Change:** Replace with `<span className="badge badge--cat-{slug}">{cat}: {count}</span>` using `catSlug()` from `../utils/catSlug`.
- **Existing CSS:** `.badge--cat-sound-design`, `.badge--cat-mixing`, etc. already defined in App.css with bg/text color pairs for all 7 categories.
- **Import needed:** `import { catSlug } from "../utils/catSlug";` (not currently imported in CreatorDetail.tsx).
- **Layout:** Replace the run-on text with a flex-wrap container holding pill badges.
### 3. Tag Overflow Limit (R027)
**Files affected (5 tag-rendering sites):**
| File | Line | Context |
|------|------|---------|
| `frontend/src/pages/Home.tsx` | ~201 | Featured technique tags |
| `frontend/src/pages/Home.tsx` | ~247 | Recent technique card tags |
| `frontend/src/pages/SearchResults.tsx` | ~143 | Search result card tags |
| `frontend/src/pages/SubTopicPage.tsx` | ~147 | Sub-topic technique card tags |
| `frontend/src/pages/CreatorDetail.tsx` | ~108 | Creator technique card tags |
**Pattern:** All 5 sites use the same idiom:
```tsx
{t.topic_tags.map(tag => <span className="pill">{tag}</span>)}
```
**Change:** Extract a shared helper or inline the pattern:
```tsx
{tags.slice(0, 4).map(tag => <span className="pill">{tag}</span>)}
{tags.length > 4 && <span className="pill pill--overflow">+{tags.length - 4} more</span>}
```
**Recommendation:** Create a small `TagList` component in `frontend/src/components/TagList.tsx` to DRY up the 5 sites. Props: `tags: string[]`, `max?: number` (default 4). The component renders pills with overflow. This avoids repeating the slice/overflow logic 5 times.
### 4. Empty Subtopic Handling (R028)
**File:** `frontend/src/pages/TopicsBrowse.tsx`
- **Current:** All subtopics render as clickable links regardless of `technique_count`.
- **Change:** When `st.technique_count === 0`, render as a non-clickable element with a "Coming soon" badge instead of a link. Keep it visible (don't hide) so users know the topic exists.
- **CSS:** Add a `.topic-subtopic--empty` modifier class with muted styling and a small "Coming soon" badge.
## Key Observations
1. **CSS color system is complete** — all 7 category color pairs exist as CSS custom properties and badge modifier classes. No new colors needed for creator stats pills.
2. **`catSlug()` utility exists** — converts category names to CSS-safe slugs (lowercase, spaces→hyphens). Already used in TopicsBrowse.tsx.
3. **S01 added `card-stagger` animation**`@keyframes cardEnter` and `.card-stagger` class with `--stagger-index` CSS variable. Already applied to topic cards and creator technique cards.
4. **No existing expand/collapse animation** — subtopics use conditional rendering (`{isExpanded && ...}`), not CSS transitions. Needs refactoring for smooth animation.
5. **`grid-template-rows` transition** is well-supported (Chrome 57+, Firefox 66+, Safari 16+) and doesn't require knowing content height.
## Natural Task Seams
1. **Topics collapse + animation (R019)** — Self-contained in TopicsBrowse.tsx + App.css. Change default state, add CSS grid transition.
2. **Creator stats pills (R026)** — Self-contained in CreatorDetail.tsx + App.css. Import catSlug, replace text with badges.
3. **Tag overflow + empty subtopics (R027, R028)** — Create TagList component, update 5 files for tag overflow, update TopicsBrowse.tsx for empty subtopics. These can be one task since TagList is the shared piece.
**Risk ordering:** Topics collapse/animation is slightly riskier (CSS transition refactor), so do it first. The others are mechanical.
## Verification
- `cd frontend && npx tsc --noEmit` — TypeScript compiles clean
- `cd frontend && npm run build` — Vite production build succeeds
- Browser verification at http://ub01:8096:
- Topics page loads collapsed, click expands with animation
- Creator detail shows colored pills for topic stats
- Cards with >4 tags show exactly 4 + "+N more"
- Empty subtopics show "Coming soon" badge

View file

@ -0,0 +1,40 @@
---
estimated_steps: 13
estimated_files: 2
skills_used: []
---
# T01: Topics page default-collapsed with expand/collapse animation
Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions.
## Steps
1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed.
2. Refactor the subtopics rendering from conditional `{isExpanded && (<div className="topic-subtopics">...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique:
- Wrap the subtopics in a `<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;`
- When `data-expanded="true"`, set `grid-template-rows: 1fr`
- The inner `<div className="topic-subtopics">` gets `overflow: hidden; min-height: 0;`
- Always render both wrapper and inner div (remove the conditional render)
3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318):
- `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }`
- `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }`
- `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule)
4. Verify: `cd frontend && npx tsc --noEmit && npm run build`
## Inputs
- `frontend/src/pages/TopicsBrowse.tsx`
- `frontend/src/App.css`
## Expected Output
- `frontend/src/pages/TopicsBrowse.tsx`
- `frontend/src/App.css`
## Verification
cd frontend && npx tsc --noEmit && npm run build

View file

@ -0,0 +1,76 @@
---
id: T01
parent: S02
milestone: M011
provides: []
requires: []
affects: []
key_files: ["frontend/src/pages/TopicsBrowse.tsx", "frontend/src/App.css"]
key_decisions: ["Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0."
completed_at: 2026-03-31T08:30:49.223Z
blocker_discovered: false
---
# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
> Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
## What Happened
---
id: T01
parent: S02
milestone: M011
key_files:
- frontend/src/pages/TopicsBrowse.tsx
- frontend/src/App.css
key_decisions:
- Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement
duration: ""
verification_result: passed
completed_at: 2026-03-31T08:30:49.224Z
blocker_discovered: false
---
# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
**Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render**
## What Happened
Changed the useEffect in TopicsBrowse.tsx to initialize the expanded set as empty instead of pre-filling all category names. Replaced the conditional {isExpanded && (...)} render with an always-rendered wrapper div using the grid-template-rows: 0fr/1fr animation technique. Added CSS rules for the wrapper, expanded state, and updated the inner div with overflow: hidden and min-height: 0 for smooth clipping.
## Verification
TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd frontend && npx tsc --noEmit && npm run build` | 0 | ✅ pass | 3700ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `frontend/src/pages/TopicsBrowse.tsx`
- `frontend/src/App.css`
## Deviations
None.
## Known Issues
None.

View file

@ -0,0 +1,52 @@
---
estimated_steps: 23
estimated_files: 2
skills_used: []
---
# T02: Creator stats topic-colored pill badges
Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes.
## Steps
1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";`
2. Find the stats section (~line 108-126) that renders `<span className="creator-detail__topic-stat">` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills:
```tsx
.map(([cat, count]) => (
<span key={cat} className={`badge badge--cat-${catSlug(cat)}`}>
{cat}: {count}
</span>
))
```
Remove the dot separator spans (`queue-card__separator`) between them.
3. Wrap the topic stat pills in a flex container: `<span className="creator-detail__topic-pills">` and add CSS in `frontend/src/App.css`:
```css
.creator-detail__topic-pills {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
align-items: center;
}
```
4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm).
5. Verify: `cd frontend && npx tsc --noEmit && npm run build`
## Inputs
- `frontend/src/pages/CreatorDetail.tsx`
- `frontend/src/App.css`
- `frontend/src/utils/catSlug.ts`
## Expected Output
- `frontend/src/pages/CreatorDetail.tsx`
- `frontend/src/App.css`
## Verification
cd frontend && npx tsc --noEmit && npm run build

View file

@ -0,0 +1,94 @@
---
estimated_steps: 56
estimated_files: 7
skills_used: []
---
# T03: TagList component, tag overflow limit, and empty subtopic handling
Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
## Steps
1. Create `frontend/src/components/TagList.tsx`:
```tsx
interface TagListProps {
tags: string[];
max?: number;
}
export default function TagList({ tags, max = 4 }: TagListProps) {
const visible = tags.slice(0, max);
const overflow = tags.length - max;
return (
<>
{visible.map(tag => <span key={tag} className="pill">{tag}</span>)}
{overflow > 0 && <span className="pill pill--overflow">+{overflow} more</span>}
</>
);
}
```
2. Add `.pill--overflow` CSS in `frontend/src/App.css`:
```css
.pill--overflow {
background: var(--color-surface-2);
color: var(--color-text-secondary);
font-style: italic;
}
```
3. Replace tag rendering in all 5 sites with `<TagList tags={...} />`:
- `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `<TagList tags={featured.topic_tags} />`
- `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
- `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `<TagList tags={item.topic_tags} />`
- `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
- `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => <span className="pill">{tag}</span>)` with `<TagList tags={...} />`.
4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`:
- Render as a `<span>` (not `<Link>`) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link
- Add a small "Coming soon" badge: `<span className="pill pill--coming-soon">Coming soon</span>`
- Keep the subtopic name visible so users know the topic exists
Use a conditional: `st.technique_count === 0 ? <span className="topic-subtopic topic-subtopic--empty">...</span> : <Link ...>...</Link>`
5. Add CSS for empty subtopic in `frontend/src/App.css`:
```css
.topic-subtopic--empty {
opacity: 0.5;
cursor: default;
}
.topic-subtopic--empty:hover {
background: transparent;
}
.pill--coming-soon {
font-size: 0.65rem;
background: var(--color-surface-2);
color: var(--color-text-secondary);
font-style: italic;
}
```
6. Verify: `cd frontend && npx tsc --noEmit && npm run build`
## Inputs
- `frontend/src/pages/Home.tsx`
- `frontend/src/pages/SearchResults.tsx`
- `frontend/src/pages/SubTopicPage.tsx`
- `frontend/src/pages/CreatorDetail.tsx`
- `frontend/src/pages/TopicsBrowse.tsx`
- `frontend/src/App.css`
## Expected Output
- `frontend/src/components/TagList.tsx`
- `frontend/src/pages/Home.tsx`
- `frontend/src/pages/SearchResults.tsx`
- `frontend/src/pages/SubTopicPage.tsx`
- `frontend/src/pages/CreatorDetail.tsx`
- `frontend/src/pages/TopicsBrowse.tsx`
- `frontend/src/App.css`
## Verification
cd frontend && npx tsc --noEmit && npm run build

View file

@ -2315,7 +2315,19 @@ a.app-footer__repo:hover {
/* ── Sub-topics inside card ───────────────────────────────────────────────── */ /* ── Sub-topics inside card ───────────────────────────────────────────────── */
.topic-subtopics-wrapper {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 300ms ease;
}
.topic-subtopics-wrapper[data-expanded="true"] {
grid-template-rows: 1fr;
}
.topic-subtopics { .topic-subtopics {
overflow: hidden;
min-height: 0;
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
} }

View file

@ -34,8 +34,8 @@ export default function TopicsBrowse() {
const data = await fetchTopics(); const data = await fetchTopics();
if (!cancelled) { if (!cancelled) {
setCategories(data); setCategories(data);
// All expanded by default // Start collapsed
setExpanded(new Set(data.map((c) => c.name))); setExpanded(new Set());
} }
} catch (err) { } catch (err) {
if (!cancelled) { if (!cancelled) {
@ -151,7 +151,7 @@ export default function TopicsBrowse() {
</button> </button>
</div> </div>
{isExpanded && ( <div className="topic-subtopics-wrapper" data-expanded={isExpanded}>
<div className="topic-subtopics"> <div className="topic-subtopics">
{cat.sub_topics.map((st) => { {cat.sub_topics.map((st) => {
const stSlug = st.name.toLowerCase().replace(/\s+/g, "-"); const stSlug = st.name.toLowerCase().replace(/\s+/g, "-");
@ -175,7 +175,7 @@ export default function TopicsBrowse() {
); );
})} })}
</div> </div>
)} </div>
</div> </div>
); );
})} })}