feat: Created AdminDropdown component with click-outside/Escape close,…

- "frontend/src/components/AdminDropdown.tsx"
- "frontend/src/App.tsx"
- "frontend/src/App.css"

GSD-Task: S01/T01
This commit is contained in:
jlightner 2026-03-30 11:02:23 +00:00
parent 94460faf9d
commit 05c7ba3ca2
13 changed files with 454 additions and 5 deletions

View file

@ -0,0 +1,14 @@
# M006:
## Vision
Consolidate admin navigation into a dropdown, add head/tail log viewing and commit SHA tracking to the pipeline, clean up technique page tag styling and sidebar order, redesign the Topics browse page with a Music Theory category, and add app version footer.
## Slice Overview
| ID | Slice | Risk | Depends | Done | After this |
|----|-------|------|---------|------|------------|
| S01 | Admin Navigation Dropdown + Header Cleanup | low | — | ⬜ | Header shows Home, Topics, Creators, and an Admin dropdown. Clicking Admin reveals Review, Reports, Pipeline links. ModeToggle removed from header. |
| S02 | Pipeline Page: Head/Tail Log View + Token Count | low | — | ⬜ | Pipeline event log shows Head/Tail toggle buttons. Head shows first N events, Tail shows last N events. Token counts visible per event and per video. |
| S03 | Git Commit SHA in Pipeline Version Metadata | low | — | ⬜ | Running the pipeline captures the current git commit SHA. Viewing a technique page version shows the commit hash in the metadata panel. |
| S04 | Technique Page: Sidebar Reorder, Creator Emphasis, Tag Polish | medium | — | ⬜ | Technique page sidebar shows Plugins Referenced at top. Creator name is visually prominent. Tags use a coherent color system. |
| S05 | Topics Page Redesign + Music Theory Category | high | — | ⬜ | Topics page shows 7 categories (including Music Theory) with an improved visual layout — category cards with descriptions, sub-topic counts, better structure. |
| S06 | App Footer with Version Info | low | — | ⬜ | Every page shows a subtle footer with app version, build date, and optional repo link. |

View file

@ -0,0 +1,34 @@
# S01: Admin Navigation Dropdown + Header Cleanup
**Goal:** Header nav shows Home, Topics, Creators as flat links and an Admin dropdown. Clicking Admin reveals Review, Reports, Pipeline links. ModeToggle removed from header (kept on ReviewQueue page).
**Demo:** After this: Header shows Home, Topics, Creators, and an Admin dropdown. Clicking Admin reveals Review, Reports, Pipeline links. ModeToggle removed from header.
## Tasks
- [x] **T01: Created AdminDropdown component with click-outside/Escape close, wired into App.tsx header replacing 3 admin links + ModeToggle, added dropdown CSS using existing theme tokens** — Create an AdminDropdown React component, replace the 3 admin nav links and ModeToggle in App.tsx, and add dropdown CSS to App.css.
**Context:** The header in `App.tsx` currently has 6 flat `<Link>` elements (Home, Topics, Creators, Review, Reports, Pipeline) plus a `<ModeToggle />`. Goal: group the 3 admin links behind an "Admin" dropdown, remove ModeToggle from the header. ModeToggle is still used by `ReviewQueue.tsx` — do NOT delete the component or its CSS.
**Click-outside pattern** — adapt from `Home.tsx` (typeahead):
```tsx
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handler(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
```
**CSS variables available** (all defined in App.css `:root`):
- Trigger text: `--color-text-on-header` / `--color-text-on-header-hover`
- Dropdown background: `--color-bg-surface`
- Border: `--color-border`
- Shadow: `--color-shadow-heavy`
- Item hover bg: `--color-bg-surface-hover`
- Item text: `--color-text-primary`
- Estimate: 30m
- Files: frontend/src/components/AdminDropdown.tsx, frontend/src/App.tsx, frontend/src/App.css
- Verify: cd frontend && npx tsc --noEmit && npx vite build

View file

@ -0,0 +1,124 @@
# S01 Research — Admin Navigation Dropdown + Header Cleanup
## Summary
Straightforward frontend slice. The header nav in `App.tsx` currently shows 6 flat links (Home, Topics, Creators, Review, Reports, Pipeline) plus a `ModeToggle` component. The goal: group the 3 admin links (Review, Reports, Pipeline) behind an "Admin" dropdown, and remove `ModeToggle` from the header (it stays on the ReviewQueue page).
No new dependencies needed. No backend changes. No library lookups required — the codebase already has a click-outside dropdown pattern in `Home.tsx` (typeahead) that can be adapted.
## Recommendation
Single task. All changes are in 2 files (`App.tsx`, `App.css`) plus optionally extracting the dropdown into a small component. The ModeToggle component file itself is NOT deleted — it's still imported by `ReviewQueue.tsx`.
## Implementation Landscape
### Current State
**`frontend/src/App.tsx`** — The app shell. Header contains:
- Brand link (`<Link to="/">Chrysopedia</Link>`)
- `<div className="app-header__right">` containing:
- `<nav className="app-nav">` with 6 `<Link>` elements: Home, Topics, Creators, Review, Reports, Pipeline
- `<ModeToggle />` component
**`frontend/src/components/ModeToggle.tsx`** — Review/auto mode toggle. Imported in:
1. `App.tsx` line 12 (header — **remove this usage**)
2. `ReviewQueue.tsx` line 16 (page body — **keep this usage**)
**`frontend/src/App.css`** — All styles. Relevant sections:
- `.app-header` — flex row, space-between, dark background (`--color-bg-header`)
- `.app-header__right` — flex, gap 1.5rem
- `.app-nav` — flex, gap 1rem, links styled with `--color-text-on-header`
- `.mode-toggle` — all mode toggle styles (lines 425-488) — **keep these** (ReviewQueue still uses them)
### Target State
Header nav shows: `Home | Topics | Creators | Admin ▾`
Clicking "Admin" reveals a small dropdown with: Review, Reports, Pipeline.
ModeToggle removed from the header entirely.
### Existing Dropdown Pattern
`Home.tsx` uses a click-outside-to-close pattern:
```
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handler(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setShowDropdown(false);
}
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
```
### Approach
1. **Create `AdminDropdown` component** (`frontend/src/components/AdminDropdown.tsx`):
- `useState<boolean>` for open/closed
- `useRef<HTMLDivElement>` for click-outside detection (same pattern as Home.tsx typeahead)
- Button that says "Admin" with a chevron indicator
- Dropdown panel with 3 `<Link>` items: Review, Reports, Pipeline
- Close dropdown on link click and on outside click
- Close on Escape key for accessibility
2. **Edit `App.tsx`**:
- Remove `ModeToggle` import
- Remove `<ModeToggle />` from header JSX
- Import `AdminDropdown`
- Replace the 3 admin `<Link>` elements with `<AdminDropdown />`
- Keep Home, Topics, Creators as flat links
3. **Add CSS to `App.css`**:
- `.admin-dropdown` — relative positioning container
- `.admin-dropdown__trigger` — styled like existing nav links (same font-size, color, transitions)
- `.admin-dropdown__menu` — absolute positioned panel below trigger, dark surface background, border, shadow, border-radius
- `.admin-dropdown__item` — block links with padding, hover state
- Use existing CSS variables: `--color-bg-surface`, `--color-border`, `--color-shadow-heavy`, `--color-text-on-header`, etc.
### Files Touched
| File | Change |
|------|--------|
| `frontend/src/components/AdminDropdown.tsx` | **New** — dropdown component |
| `frontend/src/App.tsx` | Remove ModeToggle import/usage, remove 3 admin links, add AdminDropdown |
| `frontend/src/App.css` | Add `.admin-dropdown*` styles (~30 lines) |
### Files NOT Touched
| File | Why |
|------|-----|
| `frontend/src/components/ModeToggle.tsx` | Still used by ReviewQueue.tsx — do not delete |
| `frontend/src/App.css` `.mode-toggle` styles | Still used by ReviewQueue page — do not remove |
### CSS Variable Reuse
All needed tokens already exist:
- Trigger text: `--color-text-on-header` / `--color-text-on-header-hover`
- Dropdown panel background: `--color-bg-surface`
- Dropdown border: `--color-border`
- Dropdown shadow: `--color-shadow-heavy`
- Item hover: `--color-bg-surface-hover`
- Item text: `--color-text-primary`
### Verification
- `cd frontend && npx tsc --noEmit` — zero TypeScript errors
- `cd frontend && npx vite build` — production build succeeds
- Visual: header shows Home, Topics, Creators, Admin. Clicking Admin reveals dropdown with Review, Reports, Pipeline links. No ModeToggle in header.
- Navigation: each dropdown link navigates to the correct route
- Click outside dropdown → closes
- Escape key → closes
- Mobile (640px): dropdown still accessible
### Accessibility Notes
- Dropdown trigger should use `<button>` with `aria-expanded` and `aria-haspopup="true"`
- Dropdown menu should use `role="menu"` with `role="menuitem"` on links
- Escape key should close the menu and return focus to trigger
### Risk
Low. No backend changes, no route changes, no new dependencies. The dropdown is a pure presentational addition to the existing nav.

View file

@ -0,0 +1,50 @@
---
estimated_steps: 22
estimated_files: 3
skills_used: []
---
# T01: Create AdminDropdown component, wire into header, add styles
Create an AdminDropdown React component, replace the 3 admin nav links and ModeToggle in App.tsx, and add dropdown CSS to App.css.
**Context:** The header in `App.tsx` currently has 6 flat `<Link>` elements (Home, Topics, Creators, Review, Reports, Pipeline) plus a `<ModeToggle />`. Goal: group the 3 admin links behind an "Admin" dropdown, remove ModeToggle from the header. ModeToggle is still used by `ReviewQueue.tsx` — do NOT delete the component or its CSS.
**Click-outside pattern** — adapt from `Home.tsx` (typeahead):
```tsx
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handler(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
```
**CSS variables available** (all defined in App.css `:root`):
- Trigger text: `--color-text-on-header` / `--color-text-on-header-hover`
- Dropdown background: `--color-bg-surface`
- Border: `--color-border`
- Shadow: `--color-shadow-heavy`
- Item hover bg: `--color-bg-surface-hover`
- Item text: `--color-text-primary`
## Inputs
- ``frontend/src/App.tsx` — current header with 6 flat nav links + ModeToggle import to refactor`
- ``frontend/src/App.css` — existing header/nav styles and CSS custom properties (lines 146-175 for header, line 6+ for variables)`
- ``frontend/src/components/ModeToggle.tsx` — must NOT be deleted (still imported by ReviewQueue.tsx line 16)`
- ``frontend/src/pages/Home.tsx` — click-outside dropdown pattern (useRef + mousedown event listener) to adapt`
## Expected Output
- ``frontend/src/components/AdminDropdown.tsx` — new component: useState toggle, useRef click-outside, Escape key handler, `<button>` trigger with aria-expanded + aria-haspopup='true', dropdown `<div role='menu'>` with 3 `<Link role='menuitem'>` items (Review → /admin/review, Reports → /admin/reports, Pipeline → /admin/pipeline), close on link click`
- ``frontend/src/App.tsx` — ModeToggle import line removed, `<ModeToggle />` JSX removed, AdminDropdown imported, 3 admin `<Link>` elements replaced with `<AdminDropdown />`; remaining nav: Home, Topics, Creators as flat links`
- ``frontend/src/App.css` — new styles appended: `.admin-dropdown` (position: relative), `.admin-dropdown__trigger` (styled like nav links — same font-size, color, background:none, border:none, cursor:pointer, uses --color-text-on-header), `.admin-dropdown__menu` (position: absolute, top: 100%, right: 0, min-width: 10rem, background: var(--color-bg-surface), border: 1px solid var(--color-border), border-radius: 0.5rem, box-shadow, z-index: 100), `.admin-dropdown__item` (display: block, padding, color: var(--color-text-primary), hover: var(--color-bg-surface-hover))`
## Verification
cd frontend && npx tsc --noEmit && npx vite build

View file

@ -0,0 +1,80 @@
---
id: T01
parent: S01
milestone: M006
provides: []
requires: []
affects: []
key_files: ["frontend/src/components/AdminDropdown.tsx", "frontend/src/App.tsx", "frontend/src/App.css"]
key_decisions: ["Used calc(100% + 0.5rem) for menu offset to add visual breathing room", "BEM naming convention: admin-dropdown / admin-dropdown__trigger / __menu / __item"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "TypeScript check (tsc --noEmit) passed with zero errors. Vite production build succeeded — 47 modules transformed, built in 778ms. Both slice-level verification commands pass."
completed_at: 2026-03-30T11:02:14.843Z
blocker_discovered: false
---
# T01: Created AdminDropdown component with click-outside/Escape close, wired into App.tsx header replacing 3 admin links + ModeToggle, added dropdown CSS using existing theme tokens
> Created AdminDropdown component with click-outside/Escape close, wired into App.tsx header replacing 3 admin links + ModeToggle, added dropdown CSS using existing theme tokens
## What Happened
---
id: T01
parent: S01
milestone: M006
key_files:
- frontend/src/components/AdminDropdown.tsx
- frontend/src/App.tsx
- frontend/src/App.css
key_decisions:
- Used calc(100% + 0.5rem) for menu offset to add visual breathing room
- BEM naming convention: admin-dropdown / admin-dropdown__trigger / __menu / __item
duration: ""
verification_result: passed
completed_at: 2026-03-30T11:02:14.844Z
blocker_discovered: false
---
# T01: Created AdminDropdown component with click-outside/Escape close, wired into App.tsx header replacing 3 admin links + ModeToggle, added dropdown CSS using existing theme tokens
**Created AdminDropdown component with click-outside/Escape close, wired into App.tsx header replacing 3 admin links + ModeToggle, added dropdown CSS using existing theme tokens**
## What Happened
Created AdminDropdown.tsx with useState toggle, useRef click-outside listener, Escape handler, accessible button trigger (aria-expanded, aria-haspopup), and role=menu div with 3 Link menuitem elements. Updated App.tsx to import AdminDropdown instead of ModeToggle, replaced 3 admin Link elements and ModeToggle JSX with single AdminDropdown component. Appended BEM-style dropdown CSS to App.css using existing theme custom properties. ModeToggle preserved — still imported by ReviewQueue.tsx.
## Verification
TypeScript check (tsc --noEmit) passed with zero errors. Vite production build succeeded — 47 modules transformed, built in 778ms. Both slice-level verification commands pass.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 3000ms |
| 2 | `cd frontend && npx vite build` | 0 | ✅ pass | 4400ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `frontend/src/components/AdminDropdown.tsx`
- `frontend/src/App.tsx`
- `frontend/src/App.css`
## Deviations
None.
## Known Issues
None.

View file

@ -0,0 +1,6 @@
# S02: Pipeline Page: Head/Tail Log View + Token Count
**Goal:** Add head/tail viewing to pipeline event log for quick start/end inspection
**Demo:** After this: Pipeline event log shows Head/Tail toggle buttons. Head shows first N events, Tail shows last N events. Token counts visible per event and per video.
## Tasks

View file

@ -0,0 +1,6 @@
# S03: Git Commit SHA in Pipeline Version Metadata
**Goal:** Record git commit SHA in pipeline metadata for traceability of prompt/code state at synthesis time
**Demo:** After this: Running the pipeline captures the current git commit SHA. Viewing a technique page version shows the commit hash in the metadata panel.
## Tasks

View file

@ -0,0 +1,6 @@
# S04: Technique Page: Sidebar Reorder, Creator Emphasis, Tag Polish
**Goal:** Improve technique page visual hierarchy: plugins first in sidebar, creator stands out, tags make sense
**Demo:** After this: Technique page sidebar shows Plugins Referenced at top. Creator name is visually prominent. Tags use a coherent color system.
## Tasks

View file

@ -0,0 +1,6 @@
# S05: Topics Page Redesign + Music Theory Category
**Goal:** Add Music Theory to the taxonomy, update classification prompt, and redesign the Topics browse page
**Demo:** After this: Topics page shows 7 categories (including Music Theory) with an improved visual layout — category cards with descriptions, sub-topic counts, better structure.
## Tasks

View file

@ -0,0 +1,6 @@
# S06: App Footer with Version Info
**Goal:** Add application versioning via a persistent footer
**Demo:** After this: Every page shows a subtle footer with app version, build date, and optional repo link.
## Tasks

View file

@ -776,6 +776,53 @@ body {
color: var(--color-text-on-header-hover);
}
/* ── Admin dropdown ───────────────────────────────────────────────────────── */
.admin-dropdown {
position: relative;
}
.admin-dropdown__trigger {
font-family: inherit;
font-size: 0.875rem;
color: var(--color-text-on-header);
background: none;
border: none;
cursor: pointer;
padding: 0;
transition: color 0.15s;
}
.admin-dropdown__trigger:hover {
color: var(--color-text-on-header-hover);
}
.admin-dropdown__menu {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
min-width: 10rem;
background: var(--color-bg-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
box-shadow: 0 4px 16px var(--color-shadow-heavy);
z-index: 100;
padding: 0.375rem 0;
}
.admin-dropdown__item {
display: block;
padding: 0.5rem 1rem;
color: var(--color-text-primary);
text-decoration: none;
font-size: 0.875rem;
transition: background 0.12s;
}
.admin-dropdown__item:hover {
background: var(--color-bg-surface-hover);
}
/* ── Home / Hero ──────────────────────────────────────────────────────────── */
.home-hero {

View file

@ -9,7 +9,7 @@ import ReviewQueue from "./pages/ReviewQueue";
import MomentDetail from "./pages/MomentDetail";
import AdminReports from "./pages/AdminReports";
import AdminPipeline from "./pages/AdminPipeline";
import ModeToggle from "./components/ModeToggle";
import AdminDropdown from "./components/AdminDropdown";
export default function App() {
return (
@ -23,11 +23,8 @@ export default function App() {
<Link to="/">Home</Link>
<Link to="/topics">Topics</Link>
<Link to="/creators">Creators</Link>
<Link to="/admin/review">Review</Link>
<Link to="/admin/reports">Reports</Link>
<Link to="/admin/pipeline">Pipeline</Link>
<AdminDropdown />
</nav>
<ModeToggle />
</div>
</header>

View file

@ -0,0 +1,73 @@
import { useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
export default function AdminDropdown() {
const [open, setOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Close on outside click
useEffect(() => {
function handler(e: MouseEvent) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setOpen(false);
}
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
// Close on Escape
useEffect(() => {
function handler(e: KeyboardEvent) {
if (e.key === "Escape") setOpen(false);
}
if (open) {
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}
}, [open]);
return (
<div className="admin-dropdown" ref={dropdownRef}>
<button
className="admin-dropdown__trigger"
onClick={() => setOpen((prev) => !prev)}
aria-expanded={open}
aria-haspopup="true"
>
Admin
</button>
{open && (
<div className="admin-dropdown__menu" role="menu">
<Link
to="/admin/review"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Review
</Link>
<Link
to="/admin/reports"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Reports
</Link>
<Link
to="/admin/pipeline"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Pipeline
</Link>
</div>
)}
</div>
);
}