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:
parent
717f6c0785
commit
41bf06e431
12 changed files with 720 additions and 6 deletions
|
|
@ -6,7 +6,7 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add
|
|||
## Slice Overview
|
||||
| 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. |
|
||||
| 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. |
|
||||
|
|
|
|||
106
.gsd/milestones/M011/slices/S01/S01-SUMMARY.md
Normal file
106
.gsd/milestones/M011/slices/S01/S01-SUMMARY.md
Normal 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
|
||||
77
.gsd/milestones/M011/slices/S01/S01-UAT.md
Normal file
77
.gsd/milestones/M011/slices/S01/S01-UAT.md
Normal 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)
|
||||
42
.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json
Normal file
42
.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json
Normal 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
|
||||
}
|
||||
|
|
@ -1,6 +1,125 @@
|
|||
# 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.
|
||||
|
||||
## 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
|
||||
|
|
|
|||
96
.gsd/milestones/M011/slices/S02/S02-RESEARCH.md
Normal file
96
.gsd/milestones/M011/slices/S02/S02-RESEARCH.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# S02 Research: Topics, Creator Stats & Card Polish
|
||||
|
||||
## Summary
|
||||
|
||||
Straightforward frontend-only changes across 5–6 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 ~97–110: 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
|
||||
40
.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md
Normal file
40
.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md
Normal 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
|
||||
76
.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md
Normal file
76
.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md
Normal 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.
|
||||
52
.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md
Normal file
52
.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md
Normal 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
|
||||
94
.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md
Normal file
94
.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md
Normal 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
|
||||
|
|
@ -2315,7 +2315,19 @@ a.app-footer__repo:hover {
|
|||
|
||||
/* ── 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 {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ export default function TopicsBrowse() {
|
|||
const data = await fetchTopics();
|
||||
if (!cancelled) {
|
||||
setCategories(data);
|
||||
// All expanded by default
|
||||
setExpanded(new Set(data.map((c) => c.name)));
|
||||
// Start collapsed
|
||||
setExpanded(new Set());
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
|
|
@ -151,7 +151,7 @@ export default function TopicsBrowse() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>
|
||||
<div className="topic-subtopics">
|
||||
{cat.sub_topics.map((st) => {
|
||||
const stSlug = st.name.toLowerCase().replace(/\s+/g, "-");
|
||||
|
|
@ -175,7 +175,7 @@ export default function TopicsBrowse() {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue