diff --git a/.gsd/milestones/M011/M011-ROADMAP.md b/.gsd/milestones/M011/M011-ROADMAP.md index 582c017..526f558 100644 --- a/.gsd/milestones/M011/M011-ROADMAP.md +++ b/.gsd/milestones/M011/M011-ROADMAP.md @@ -8,5 +8,5 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add |----|-------|------|---------|------|------------| | 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. | +| 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. | diff --git a/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md b/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md new file mode 100644 index 0000000..97a6f39 --- /dev/null +++ b/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md @@ -0,0 +1,85 @@ +--- +id: S03 +parent: M011 +milestone: M011 +provides: + - Compact nav search bar on all non-home pages with Cmd+K focus + - Mobile hamburger menu with stacked nav links at <768px +requires: + [] +affects: + - S04 +key_files: + - frontend/src/components/SearchAutocomplete.tsx + - frontend/src/App.tsx + - frontend/src/App.css + - frontend/src/pages/Home.tsx + - frontend/src/pages/SearchResults.tsx +key_decisions: + - Refactored SearchAutocomplete from heroSize boolean to variant string prop for cleaner multi-context usage + - Mobile nav search uses second component instance (no globalShortcut) rather than CSS repositioning to avoid double keyboard handler registration +patterns_established: + - Variant prop pattern for component display modes (hero/inline/nav) — reusable for other components that need context-dependent sizing +observability_surfaces: + - none +drill_down_paths: + - .gsd/milestones/M011/slices/S03/tasks/T01-SUMMARY.md + - .gsd/milestones/M011/slices/S03/tasks/T02-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-03-31T08:46:50.106Z +blocker_discovered: false +--- + +# S03: Global Search & Mobile Navigation + +**Added compact nav search bar with Cmd+K shortcut on all non-home pages and mobile hamburger menu with 44px touch targets and three auto-close mechanisms.** + +## What Happened + +Two tasks delivered the slice goal cleanly. T01 refactored SearchAutocomplete from a boolean heroSize prop to a variant string prop ('hero' | 'inline' | 'nav'), added a globalShortcut prop for Cmd+K/Ctrl+K keyboard focus, created nav-variant CSS with compact sizing and high z-index dropdown, and wired the nav search into App.tsx conditionally on non-home routes. Home.tsx and SearchResults.tsx callers were updated to use the new variant prop. The deprecated heroSize prop remains as a backwards-compat fallback. + +T02 added a hamburger menu button visible below 768px that toggles a mobile nav panel. Three auto-close mechanisms were implemented: route change (useEffect on location.pathname), Escape key (keydown listener), and outside click (ref-based click detection). The hamburger icon toggles between three-line and X via conditional SVG. All mobile nav links get min-height: 44px touch targets. A second SearchAutocomplete instance (without globalShortcut to avoid double registration) renders inside the mobile panel. AdminDropdown was restyled to full-width static submenu in the mobile panel. + +Build passes cleanly (51 modules, 813ms). Both tasks verified via build and browser testing. + +## Verification + +Frontend build succeeds: `cd frontend && npm run build` — 51 modules transformed, built in 813ms, zero errors. Code inspection confirms: variant prop with three modes, globalShortcut Cmd+K handler, hamburger button with aria-expanded, menuOpen state with three close mechanisms, 44px min-height on mobile nav links, nav search conditional on non-home routes. + +## Requirements Advanced + +- R020 — Nav search bar rendered on all non-home pages with Cmd+K/Ctrl+K keyboard shortcut +- R021 — Hamburger menu visible below 768px with 44px touch targets, three auto-close mechanisms + +## Requirements Validated + +None. + +## New Requirements Surfaced + +None. + +## Requirements Invalidated or Re-scoped + +None. + +## Deviations + +SearchAutocomplete kept deprecated heroSize prop as fallback (variant takes precedence). Mobile nav search rendered as second component instance rather than CSS-repositioned — avoids double Cmd+K registration. AdminDropdown restyled to full-width in mobile panel. + +## Known Limitations + +None. + +## Follow-ups + +None. + +## Files Created/Modified + +- `frontend/src/components/SearchAutocomplete.tsx` — Replaced heroSize boolean with variant prop, added globalShortcut prop for Cmd+K, nav-variant rendering +- `frontend/src/App.tsx` — Added hamburger menu state, three auto-close mechanisms, conditional nav search, mobile menu panel +- `frontend/src/App.css` — Nav search compact styles, hamburger button, mobile breakpoint panel, 44px touch targets +- `frontend/src/pages/Home.tsx` — Updated caller to variant="hero" +- `frontend/src/pages/SearchResults.tsx` — Updated caller to variant="inline" diff --git a/.gsd/milestones/M011/slices/S03/S03-UAT.md b/.gsd/milestones/M011/slices/S03/S03-UAT.md new file mode 100644 index 0000000..189099b --- /dev/null +++ b/.gsd/milestones/M011/slices/S03/S03-UAT.md @@ -0,0 +1,167 @@ +# S03: Global Search & Mobile Navigation — UAT + +**Milestone:** M011 +**Written:** 2026-03-31T08:46:50.106Z + +# S03 UAT: Global Search & Mobile Navigation + +## Preconditions +- Chrysopedia frontend running (dev server or production build) +- Browser with DevTools available for viewport resizing + +--- + +## Test 1: Nav Search Bar Visibility + +**Steps:** +1. Navigate to homepage (`/`) +2. Observe the navigation header + +**Expected:** No search bar in the nav header (homepage has its own hero search) + +3. Navigate to `/topics` +4. Observe the navigation header + +**Expected:** Compact search input visible in the nav bar between brand and right section + +5. Navigate to `/creators`, then any `/technique/:slug` page + +**Expected:** Search bar present in nav on both pages + +--- + +## Test 2: Nav Search Functionality + +**Steps:** +1. On `/topics`, click the nav search input +2. Type "reverb" +3. Observe typeahead dropdown + +**Expected:** Typeahead dropdown appears with results, positioned with high z-index over page content + +4. Press Enter + +**Expected:** Navigates to search results page for "reverb" + +--- + +## Test 3: Cmd+K Keyboard Shortcut + +**Steps:** +1. Navigate to `/creators` +2. Click somewhere on the page body (not in search) +3. Press Cmd+K (Mac) or Ctrl+K (Windows/Linux) + +**Expected:** Nav search input receives focus. Browser default Cmd+K behavior is prevented. + +4. Type a query and press Enter + +**Expected:** Search executes normally + +--- + +## Test 4: Cmd+K Not Active on Homepage + +**Steps:** +1. Navigate to homepage (`/`) +2. Press Cmd+K + +**Expected:** No nav search to focus (homepage uses hero search). Browser default may trigger. + +--- + +## Test 5: Hamburger Menu Visibility + +**Steps:** +1. Open DevTools, set viewport to 390×844 (iPhone 14) +2. Navigate to any page + +**Expected:** Hamburger button (☰) visible in header. Desktop nav links hidden. + +3. Set viewport to 1024×768 (desktop) + +**Expected:** Hamburger button hidden. Desktop nav links visible inline. + +--- + +## Test 6: Hamburger Menu Toggle + +**Steps:** +1. At mobile viewport (390px wide), tap hamburger button + +**Expected:** Nav panel slides open below header. Hamburger icon changes to X. Links stacked vertically. + +2. Tap X button + +**Expected:** Nav panel closes with transition. Icon reverts to ☰. + +--- + +## Test 7: Mobile Touch Targets + +**Steps:** +1. At mobile viewport, open hamburger menu +2. Inspect nav links in DevTools + +**Expected:** Each nav link has min-height of 44px. Padding provides comfortable touch area. + +--- + +## Test 8: Auto-Close on Route Change + +**Steps:** +1. At mobile viewport, open hamburger menu +2. Tap a nav link (e.g., "Topics") + +**Expected:** Page navigates to Topics. Menu closes automatically. + +--- + +## Test 9: Auto-Close on Escape + +**Steps:** +1. At mobile viewport, open hamburger menu +2. Press Escape key + +**Expected:** Menu closes. Focus returns to page. + +--- + +## Test 10: Auto-Close on Outside Click + +**Steps:** +1. At mobile viewport, open hamburger menu +2. Tap/click on the page content area below the menu + +**Expected:** Menu closes. + +--- + +## Test 11: Mobile Search in Menu + +**Steps:** +1. At mobile viewport, open hamburger menu +2. Locate search input inside the menu panel + +**Expected:** Search input is present and full-width inside mobile menu. + +3. Type a query and submit + +**Expected:** Search executes, menu closes on navigation. + +--- + +## Test 12: Desktop Layout Unchanged + +**Steps:** +1. At desktop viewport (1280px wide), navigate through all pages + +**Expected:** No hamburger button. Nav links displayed inline. Search bar compact in header on non-home pages. No layout regressions from S01/S02 work. + +--- + +## Edge Cases + +- **Rapid toggle:** Quickly tap hamburger open/close 5 times — no stuck state +- **Resize while open:** Open menu at mobile width, drag viewport to desktop width — menu should close or hide gracefully +- **Multiple Cmd+K presses:** Press Cmd+K repeatedly — input stays focused, no errors in console diff --git a/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json b/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json new file mode 100644 index 0000000..7e3f529 --- /dev/null +++ b/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "taskId": "T02", + "unitId": "M011/S03/T02", + "timestamp": 1774946733530, + "passed": true, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 7, + "verdict": "pass" + } + ] +} diff --git a/.gsd/milestones/M011/slices/S04/S04-PLAN.md b/.gsd/milestones/M011/slices/S04/S04-PLAN.md index 1718769..185ae58 100644 --- a/.gsd/milestones/M011/slices/S04/S04-PLAN.md +++ b/.gsd/milestones/M011/slices/S04/S04-PLAN.md @@ -1,6 +1,36 @@ # S04: Accessibility & SEO Fixes -**Goal:** Bring the site to WCAG 2.1 Level AA on core metrics and add SEO page titles +**Goal:** Every page has a single H1, skip-to-content link, AA-compliant text contrast, and a descriptive browser tab title. **Demo:** After this: Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. ## Tasks +- [x] **T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance** — Three accessibility fixes that all touch App.tsx/App.css and page heading elements: + +1. **Heading hierarchy (R022):** Demote `

Chrysopedia

` in App.tsx nav to ``. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `

` to `

` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `

` to SearchResults (currently has no heading). Fix heading level skips in Home.tsx: the 'How It Works' h3s (lines 120/127/134) become h2s; 'Featured Technique' h3 (line 191) becomes h2; 'Recently Added' h3 (line 221) becomes h2. + +2. **Skip-to-content link (R023):** Add `id="main-content"` to the `
` tag in App.tsx. Add `Skip to content` as the first child of `.app`. Add `.skip-link` CSS: visually hidden by default, visible on `:focus`, positioned at top of viewport. + +3. **Contrast fix (R024):** Change `--color-text-muted` from `#6b6b7a` to `#828291` in App.css `:root` block. This achieves 5.05:1 on page bg and 4.56:1 on surface bg — both above AA 4.5:1 threshold. + - Estimate: 30m + - Files: frontend/src/App.tsx, frontend/src/App.css, frontend/src/pages/Home.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/pages/CreatorsBrowse.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/AdminReports.tsx, frontend/src/pages/AdminPipeline.tsx + - Verify: cd frontend && npx tsc --noEmit && npm run build +- [ ] **T02: Add useDocumentTitle hook and wire descriptive titles into all pages** — Create a `useDocumentTitle` custom hook and call it from every page component so browser tabs show descriptive, route-specific titles. + +1. Create `frontend/src/hooks/useDocumentTitle.ts` with a hook that sets `document.title` on mount and when the title string changes. Reset to 'Chrysopedia' on unmount (cleanup). + +2. Wire the hook into all 10 page components with these title patterns: + - Home: `Chrysopedia — Production Knowledge, Distilled` + - TopicsBrowse: `Topics — Chrysopedia` + - SubTopicPage: `{subtopic} — {category} — Chrysopedia` (dynamic, updates when data loads) + - CreatorsBrowse: `Creators — Chrysopedia` + - CreatorDetail: `{name} — Chrysopedia` (dynamic, updates when creator loads) + - TechniquePage: `{title} — Chrysopedia` (dynamic, updates when technique loads) + - SearchResults: `Search: {query} — Chrysopedia` (dynamic, updates with query param) + - About: `About — Chrysopedia` + - AdminReports: `Content Reports — Chrysopedia` + - AdminPipeline: `Pipeline Management — Chrysopedia` + +3. For dynamic pages (SubTopicPage, CreatorDetail, TechniquePage, SearchResults), call the hook with the resolved value so the title updates when async data loads. Use a fallback like `Chrysopedia` while loading. + - Estimate: 25m + - Files: frontend/src/hooks/useDocumentTitle.ts, frontend/src/pages/Home.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/CreatorsBrowse.tsx, frontend/src/pages/CreatorDetail.tsx, frontend/src/pages/TechniquePage.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/About.tsx, frontend/src/pages/AdminReports.tsx, frontend/src/pages/AdminPipeline.tsx + - Verify: cd frontend && npx tsc --noEmit && npm run build diff --git a/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md b/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md new file mode 100644 index 0000000..8f4a176 --- /dev/null +++ b/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md @@ -0,0 +1,103 @@ +# S04 Research: Accessibility & SEO Fixes + +## Summary + +Straightforward accessibility fixes across known files using established patterns. Four requirements: heading hierarchy (R022), skip-to-content link (R023), text contrast AA compliance (R024), and page-specific document titles (R025). No new dependencies, no architectural decisions, no unfamiliar technology. + +## Recommendation + +Four independent tasks, one per requirement. All touch the same small set of files (App.tsx, App.css, and page components). Order doesn't matter — none depend on each other. The heading hierarchy fix touches the most files and has the most nuance (promoting h2→h1 on some pages, demoting nav h1→span, fixing heading level skips). + +## Implementation Landscape + +### R022 — Heading Hierarchy Fix + +**Current state:** `App.tsx:63` has `

Chrysopedia

` in the nav brand link. This means every page has *two* h1s — the nav one plus the page's own (Home, About, CreatorDetail, TechniquePage each have explicit `

`). + +**Pages with their own h1 (need nav h1 removed):** +- `Home.tsx:101` — `

Production Knowledge, Distilled

` +- `About.tsx:7` — `

About Chrysopedia

` +- `CreatorDetail.tsx:98` — `

{creator.name}

` +- `TechniquePage.tsx:243` — `

{displayTitle}

` + +**Pages WITHOUT h1 (need their h2 promoted to h1):** +- `TopicsBrowse.tsx:96` — `

Topics

` → promote to h1 +- `CreatorsBrowse.tsx:89` — `

Creators

` → promote to h1 +- `SubTopicPage.tsx:112` — `

{subtopicDisplay}

` → promote to h1 +- `SearchResults.tsx` — no heading at all, needs an h1 added +- `AdminReports.tsx:116` — `

Content Reports

` → promote to h1 +- `AdminPipeline.tsx:577` — `

Pipeline Management

` → promote to h1 + +**Fix:** Change `App.tsx:63` from `

` to `` (visually styled the same via CSS class). Promote h2→h1 on pages that lack one. Update CSS selectors if any target `h1` inside the brand link. + +**Heading level skips:** +- `Home.tsx` has h1 → h3 (skips h2) in the "How It Works" section. The h3s at lines 120/127/134 should be h2 or the section needs restructuring. Also h3 at line 191 (Featured label) and line 221 (Recently Added) — these should be h2. +- `SearchResults.tsx` uses h3 for group titles — should be h2 under the new h1. + +### R023 — Skip-to-Content Link + +**Current state:** No skip link exists. `
` at `App.tsx:114` is the target. + +**Fix:** Add `id="main-content"` to the `
` tag. Add a visually-hidden skip link as the first child of `.app`: `Skip to content`. CSS: position offscreen by default, visible on `:focus`. + +### R024 — Text Contrast AA Compliance + +**Current values (`:root` in App.css lines 14-16):** +- `--color-text-primary: #e2e2ea` — passes easily +- `--color-text-secondary: #8b8b9a` — **5.70:1 on page bg, 5.14:1 on surface** → already passes AA (4.5:1) +- `--color-text-muted: #6b6b7a` — **3.65:1 on page bg, 3.29:1 on surface** → **fails AA** + +**Background colors:** +- `--color-bg-page: #0f0f14` +- `--color-bg-surface: #1a1a24` + +**Fix:** Bump `--color-text-muted` from `#6b6b7a` to `#828291`. This gives 5.05:1 on page bg and 4.56:1 on surface — comfortably above 4.5:1 AA threshold while maintaining the visual hierarchy (muted < secondary < primary). + +Header text colors (`rgba(255,255,255,0.8)` etc.) all pass easily against the dark header background. + +### R025 — Page-Specific Document Titles + +**Current state:** No `document.title` usage anywhere in the codebase. Browser tab shows the default from `index.html`. + +**Fix:** Add a `useDocumentTitle` custom hook (or inline `useEffect` in each page) that sets `document.title` on mount/update. Pattern: + +```typescript +// In each page component: +useEffect(() => { + document.title = "Topics — Chrysopedia"; +}, []); + +// For dynamic pages: +useEffect(() => { + if (creator) document.title = `${creator.name} — Chrysopedia`; +}, [creator]); +``` + +**Pages needing titles (10 total):** +| Page | Title pattern | +|------|---------------| +| Home | `Chrysopedia — Production Knowledge, Distilled` | +| TopicsBrowse | `Topics — Chrysopedia` | +| SubTopicPage | `{subtopic} — {category} — Chrysopedia` | +| CreatorsBrowse | `Creators — Chrysopedia` | +| CreatorDetail | `{name} — Chrysopedia` | +| TechniquePage | `{title} — Chrysopedia` | +| SearchResults | `Search: {query} — Chrysopedia` | +| About | `About — Chrysopedia` | +| AdminReports | `Content Reports — Chrysopedia` | +| AdminPipeline | `Pipeline Management — Chrysopedia` | + +A shared `useDocumentTitle(title: string)` hook keeps it DRY. + +## Verification + +- **Heading hierarchy:** Browser DevTools → `document.querySelectorAll('h1')` returns exactly 1 per page. Check with `$$('h1, h2, h3, h4, h5, h6').map(h => h.tagName)` — levels must be sequential. +- **Skip link:** Tab from fresh page load → first focus shows "Skip to content" link → activating it scrolls to main content. +- **Contrast:** The fix is a single CSS variable change. Verify with browser DevTools color picker contrast checker or computed values. +- **Document titles:** Navigate to each route and check `document.title` in console. +- **Build:** `cd frontend && npx tsc --noEmit && npm run build` — zero errors. + +## Pitfalls + +- **CSS selectors targeting `h1` in nav brand:** Check if any styles use `.app-header__brand h1` or similar. These need updating to target `span` after the heading demotion. +- **Heading level cascade in Home.tsx:** The "How It Works" section jumps h1→h3. Simply promoting to h2 is correct since h1 is the hero title and these are section-level headings below it. diff --git a/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md b/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md new file mode 100644 index 0000000..4e0c401 --- /dev/null +++ b/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md @@ -0,0 +1,43 @@ +--- +estimated_steps: 4 +estimated_files: 9 +skills_used: [] +--- + +# T01: Fix heading hierarchy, add skip-to-content link, and fix muted text contrast + +Three accessibility fixes that all touch App.tsx/App.css and page heading elements: + +1. **Heading hierarchy (R022):** Demote `

Chrysopedia

` in App.tsx nav to ``. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `

` to `

` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `

` to SearchResults (currently has no heading). Fix heading level skips in Home.tsx: the 'How It Works' h3s (lines 120/127/134) become h2s; 'Featured Technique' h3 (line 191) becomes h2; 'Recently Added' h3 (line 221) becomes h2. + +2. **Skip-to-content link (R023):** Add `id="main-content"` to the `
` tag in App.tsx. Add `Skip to content` as the first child of `.app`. Add `.skip-link` CSS: visually hidden by default, visible on `:focus`, positioned at top of viewport. + +3. **Contrast fix (R024):** Change `--color-text-muted` from `#6b6b7a` to `#828291` in App.css `:root` block. This achieves 5.05:1 on page bg and 4.56:1 on surface bg — both above AA 4.5:1 threshold. + +## Inputs + +- ``frontend/src/App.tsx` — nav h1 to demote, main tag needs id, skip link insertion point` +- ``frontend/src/App.css` — `.app-header h1` rule to update, `:root` muted color to fix, skip-link styles to add` +- ``frontend/src/pages/Home.tsx` — h3 level skips to fix (lines 120/127/134/164/170/191/221)` +- ``frontend/src/pages/TopicsBrowse.tsx` — h2 to promote to h1 (line 96)` +- ``frontend/src/pages/CreatorsBrowse.tsx` — h2 to promote to h1 (line 89)` +- ``frontend/src/pages/SubTopicPage.tsx` — h2 to promote to h1 (line 112)` +- ``frontend/src/pages/SearchResults.tsx` — needs h1 added (has no heading currently)` +- ``frontend/src/pages/AdminReports.tsx` — h2 to promote to h1 (line 116)` +- ``frontend/src/pages/AdminPipeline.tsx` — h2 to promote to h1 (line 577)` + +## Expected Output + +- ``frontend/src/App.tsx` — nav brand uses `` instead of `

`, skip link added, main has id` +- ``frontend/src/App.css` — `.app-header h1` changed to `.app-header__brand span`, skip-link styles added, `--color-text-muted` updated to `#828291`` +- ``frontend/src/pages/Home.tsx` — heading levels fixed (h3→h2 for How It Works, Featured, Recently Added, nav cards)` +- ``frontend/src/pages/TopicsBrowse.tsx` — h2→h1 promoted` +- ``frontend/src/pages/CreatorsBrowse.tsx` — h2→h1 promoted` +- ``frontend/src/pages/SubTopicPage.tsx` — h2→h1 promoted` +- ``frontend/src/pages/SearchResults.tsx` — h1 heading added` +- ``frontend/src/pages/AdminReports.tsx` — h2→h1 promoted` +- ``frontend/src/pages/AdminPipeline.tsx` — h2→h1 promoted` + +## Verification + +cd frontend && npx tsc --noEmit && npm run build diff --git a/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md b/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md new file mode 100644 index 0000000..8b6b0e7 --- /dev/null +++ b/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md @@ -0,0 +1,92 @@ +--- +id: T01 +parent: S04 +milestone: M011 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/App.tsx", "frontend/src/App.css", "frontend/src/pages/Home.tsx", "frontend/src/pages/SearchResults.tsx", "frontend/src/pages/TopicsBrowse.tsx", "frontend/src/pages/CreatorsBrowse.tsx", "frontend/src/pages/SubTopicPage.tsx", "frontend/src/pages/AdminReports.tsx", "frontend/src/pages/AdminPipeline.tsx"] +key_decisions: ["Used visually-hidden sr-only h1 for SearchResults since the page has no visible heading", "Changed nav brand from h1 to span preserving existing CSS styling approach"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "TypeScript compilation (tsc --noEmit) and production build (npm run build) both pass with exit code 0. Verified each page has exactly one h1, skip-link and main-content id are present in App.tsx, and muted color token is updated." +completed_at: 2026-03-31T08:52:36.572Z +blocker_discovered: false +--- + +# T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance + +> Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance + +## What Happened +--- +id: T01 +parent: S04 +milestone: M011 +key_files: + - frontend/src/App.tsx + - frontend/src/App.css + - frontend/src/pages/Home.tsx + - frontend/src/pages/SearchResults.tsx + - frontend/src/pages/TopicsBrowse.tsx + - frontend/src/pages/CreatorsBrowse.tsx + - frontend/src/pages/SubTopicPage.tsx + - frontend/src/pages/AdminReports.tsx + - frontend/src/pages/AdminPipeline.tsx +key_decisions: + - Used visually-hidden sr-only h1 for SearchResults since the page has no visible heading + - Changed nav brand from h1 to span preserving existing CSS styling approach +duration: "" +verification_result: passed +completed_at: 2026-03-31T08:52:36.572Z +blocker_discovered: false +--- + +# T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance + +**Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance** + +## What Happened + +Applied three accessibility fixes across 9 files: (1) Fixed heading hierarchy by demoting nav h1 to span and promoting h2→h1 on all page components, added sr-only h1 to SearchResults, fixed h3→h2 level skips in Home.tsx. (2) Added skip-to-content link with keyboard-focusable skip-link pattern. (3) Changed --color-text-muted from #6b6b7a to #828291 for AA contrast compliance. + +## Verification + +TypeScript compilation (tsc --noEmit) and production build (npm run build) both pass with exit code 0. Verified each page has exactly one h1, skip-link and main-content id are present in App.tsx, and muted color token is updated. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 2600ms | +| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 3100ms | + + +## Deviations + +None. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/App.tsx` +- `frontend/src/App.css` +- `frontend/src/pages/Home.tsx` +- `frontend/src/pages/SearchResults.tsx` +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/pages/CreatorsBrowse.tsx` +- `frontend/src/pages/SubTopicPage.tsx` +- `frontend/src/pages/AdminReports.tsx` +- `frontend/src/pages/AdminPipeline.tsx` + + +## Deviations +None. + +## Known Issues +None. diff --git a/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md b/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md new file mode 100644 index 0000000..3547f25 --- /dev/null +++ b/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md @@ -0,0 +1,56 @@ +--- +estimated_steps: 14 +estimated_files: 11 +skills_used: [] +--- + +# T02: Add useDocumentTitle hook and wire descriptive titles into all pages + +Create a `useDocumentTitle` custom hook and call it from every page component so browser tabs show descriptive, route-specific titles. + +1. Create `frontend/src/hooks/useDocumentTitle.ts` with a hook that sets `document.title` on mount and when the title string changes. Reset to 'Chrysopedia' on unmount (cleanup). + +2. Wire the hook into all 10 page components with these title patterns: + - Home: `Chrysopedia — Production Knowledge, Distilled` + - TopicsBrowse: `Topics — Chrysopedia` + - SubTopicPage: `{subtopic} — {category} — Chrysopedia` (dynamic, updates when data loads) + - CreatorsBrowse: `Creators — Chrysopedia` + - CreatorDetail: `{name} — Chrysopedia` (dynamic, updates when creator loads) + - TechniquePage: `{title} — Chrysopedia` (dynamic, updates when technique loads) + - SearchResults: `Search: {query} — Chrysopedia` (dynamic, updates with query param) + - About: `About — Chrysopedia` + - AdminReports: `Content Reports — Chrysopedia` + - AdminPipeline: `Pipeline Management — Chrysopedia` + +3. For dynamic pages (SubTopicPage, CreatorDetail, TechniquePage, SearchResults), call the hook with the resolved value so the title updates when async data loads. Use a fallback like `Chrysopedia` while loading. + +## Inputs + +- ``frontend/src/pages/Home.tsx` — needs useDocumentTitle call` +- ``frontend/src/pages/TopicsBrowse.tsx` — needs useDocumentTitle call` +- ``frontend/src/pages/SubTopicPage.tsx` — needs dynamic useDocumentTitle call with subtopic/category` +- ``frontend/src/pages/CreatorsBrowse.tsx` — needs useDocumentTitle call` +- ``frontend/src/pages/CreatorDetail.tsx` — needs dynamic useDocumentTitle call with creator name` +- ``frontend/src/pages/TechniquePage.tsx` — needs dynamic useDocumentTitle call with technique title` +- ``frontend/src/pages/SearchResults.tsx` — needs dynamic useDocumentTitle call with search query` +- ``frontend/src/pages/About.tsx` — needs useDocumentTitle call` +- ``frontend/src/pages/AdminReports.tsx` — needs useDocumentTitle call` +- ``frontend/src/pages/AdminPipeline.tsx` — needs useDocumentTitle call` + +## Expected Output + +- ``frontend/src/hooks/useDocumentTitle.ts` — new custom hook file` +- ``frontend/src/pages/Home.tsx` — useDocumentTitle wired` +- ``frontend/src/pages/TopicsBrowse.tsx` — useDocumentTitle wired` +- ``frontend/src/pages/SubTopicPage.tsx` — useDocumentTitle wired with dynamic subtopic/category` +- ``frontend/src/pages/CreatorsBrowse.tsx` — useDocumentTitle wired` +- ``frontend/src/pages/CreatorDetail.tsx` — useDocumentTitle wired with dynamic creator name` +- ``frontend/src/pages/TechniquePage.tsx` — useDocumentTitle wired with dynamic technique title` +- ``frontend/src/pages/SearchResults.tsx` — useDocumentTitle wired with dynamic query` +- ``frontend/src/pages/About.tsx` — useDocumentTitle wired` +- ``frontend/src/pages/AdminReports.tsx` — useDocumentTitle wired` +- ``frontend/src/pages/AdminPipeline.tsx` — useDocumentTitle wired` + +## Verification + +cd frontend && npx tsc --noEmit && npm run build diff --git a/frontend/src/App.css b/frontend/src/App.css index 4f16779..5376611 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -13,7 +13,7 @@ /* Text */ --color-text-primary: #e2e2ea; --color-text-secondary: #8b8b9a; - --color-text-muted: #6b6b7a; + --color-text-muted: #828291; --color-text-active: #e2e2ea; --color-text-on-header: rgba(255, 255, 255, 0.8); --color-text-on-header-hover: #fff; @@ -155,6 +155,20 @@ body { background: var(--color-bg-page); } +/* ── Utility ──────────────────────────────────────────────────────────────── */ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + /* ── App shell ────────────────────────────────────────────────────────────── */ .app { @@ -163,6 +177,26 @@ body { min-height: 100vh; } +/* ── Skip-to-content link ─────────────────────────────────────────────────── */ + +.skip-link { + position: absolute; + left: -9999px; + top: 0; + z-index: 999; + padding: 0.5rem 1rem; + background: var(--color-accent); + color: var(--color-bg-page); + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; + border-radius: 0 0 0.375rem 0; +} + +.skip-link:focus { + left: 0; +} + .app-header { display: flex; align-items: center; @@ -172,7 +206,7 @@ body { color: var(--color-text-on-header-hover); } -.app-header h1 { +.app-header__brand span { font-size: 1.125rem; font-weight: 600; letter-spacing: -0.01em; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 98955df..6d36267 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -58,9 +58,10 @@ export default function App() { return (
+ Skip to content
-

Chrysopedia

+ Chrysopedia {showNavSearch && (
-
+
{/* Public routes */} } /> diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx index e462f95..f3f02f6 100644 --- a/frontend/src/pages/AdminPipeline.tsx +++ b/frontend/src/pages/AdminPipeline.tsx @@ -574,7 +574,7 @@ export default function AdminPipeline() {
-

Pipeline Management

+

Pipeline Management

{videos.length} video{videos.length !== 1 ? "s" : ""}

diff --git a/frontend/src/pages/AdminReports.tsx b/frontend/src/pages/AdminReports.tsx index 166cfdb..a91deb8 100644 --- a/frontend/src/pages/AdminReports.tsx +++ b/frontend/src/pages/AdminReports.tsx @@ -113,7 +113,7 @@ export default function AdminReports() { return (
-

Content Reports

+

Content Reports

{total} report{total !== 1 ? "s" : ""} total

diff --git a/frontend/src/pages/CreatorsBrowse.tsx b/frontend/src/pages/CreatorsBrowse.tsx index e0a92a8..9cb0256 100644 --- a/frontend/src/pages/CreatorsBrowse.tsx +++ b/frontend/src/pages/CreatorsBrowse.tsx @@ -86,7 +86,7 @@ export default function CreatorsBrowse() { return (
-

Creators

+

Creators

Discover creators and their technique libraries

diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index ed8edea..6371f0f 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -117,21 +117,21 @@ export default function Home() {
1 -

Creators Share Techniques

+

Creators Share Techniques

Real producers and sound designers publish in-depth tutorials

2 -

AI Extracts Key Moments

+

AI Extracts Key Moments

We distill hours of video into structured, searchable knowledge

3 -

You Find Answers Fast

+

You Find Answers Fast

Search by topic, technique, or creator — get straight to the insight

@@ -161,13 +161,13 @@ export default function Home() { {/* Navigation cards */}
-

Topics

+

Topics

Browse techniques organized by category and sub-topic

-

Creators

+

Creators

Discover creators and their technique libraries

@@ -188,7 +188,7 @@ export default function Home() { {/* Featured Technique Spotlight */} {featured && (
-

Featured Technique

+

Featured Technique

{featured.title} @@ -218,7 +218,7 @@ export default function Home() { {/* Recently Added */}
-

Recently Added

+

Recently Added

{recentLoading ? (
Loading…
) : recent.length === 0 ? ( diff --git a/frontend/src/pages/SearchResults.tsx b/frontend/src/pages/SearchResults.tsx index e2ad498..26cb3dd 100644 --- a/frontend/src/pages/SearchResults.tsx +++ b/frontend/src/pages/SearchResults.tsx @@ -52,6 +52,7 @@ export default function SearchResults() { return (
+

Search Results

{/* Inline search bar */} {subtopicDisplay} -

{subtopicDisplay}

+

{subtopicDisplay}

{categoryDisplay} · diff --git a/frontend/src/pages/TopicsBrowse.tsx b/frontend/src/pages/TopicsBrowse.tsx index 8543df8..eea0412 100644 --- a/frontend/src/pages/TopicsBrowse.tsx +++ b/frontend/src/pages/TopicsBrowse.tsx @@ -93,7 +93,7 @@ export default function TopicsBrowse() { return (

-

Topics

+

Topics

Browse techniques organized by category and sub-topic