SKILL.md
Navigation Structure Validator
Validates correct usage of PlayerPageLayout component. Ensures detail pages use it, top-level list pages don't. Use before committing new pages or during code review.
Validation Workflow
Copy this checklist to track progress:
Navigation Validation Progress:
- [ ] Step 1: Identify page type (detail vs list)
- [ ] Step 2: Check PlayerPageLayout usage
- [ ] Step 3: Verify isDetailView configuration
- [ ] Step 4: Generate violation report
- [ ] Step 5: Provide fix recommendations
Step 1: Identify Page Type
Detail Pages (SHOULD use PlayerPageLayout)
Detail pages show a single entity with specific context:
Examples:
- Series detail page:
/player/series/:seriesId - Tour detail page:
/player/tours/:tourId - Competition detail page:
/player/competitions/:competitionId - Player profile:
/player/players/:playerId - User profile:
/player/profile - Game setup wizard:
/player/games/new - Game play view:
/player/games/:gameId/play - Tee time details:
/player/competitions/:competitionId/tee-times/:teeTimeId - Document detail:
/player/series/:seriesId/documents/:docId
Pattern: URL includes dynamic ID parameter (:id, :seriesId, etc.)
Top-Level List Pages (SHOULD NOT use PlayerPageLayout)
List pages show collections without specific entity context:
Examples:
- Landing page:
/player - All competitions:
/player/competitions - All series:
/player/series - All tours:
/player/tours - All rounds:
/player/rounds - Dashboard views
Pattern: URL is static (no dynamic ID parameters)
Step 2: Check PlayerPageLayout Usage
For New or Modified Pages
Check file imports:
grep -n "PlayerPageLayout" [file_path]
Check component usage:
grep -A5 "<PlayerPageLayout" [file_path]
Validate Pattern
Detail page (✅ Correct):
import { PlayerPageLayout } from "@/components/layout/PlayerPageLayout"
export default function SeriesDetailPage() {
return (
<PlayerPageLayout
title="Series Name"
seriesId={seriesId}
showBackButton={true}
>
{/* Detail content */}
</PlayerPageLayout>
)
}
List page (✅ Correct):
export default function SeriesListPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-scorecard to-rough">
<div className="container mx-auto px-4 py-8">
{/* List content */}
</div>
</div>
)
}
Step 3: Verify isDetailView Configuration
When adding NEW detail pages, check PlayerLayout.tsx:
cat frontend/src/views/player/PlayerLayout.tsx | grep -A30 "isDetailView"
The isDetailView logic must include the new route:
const isDetailView =
location.pathname.endsWith("/player") ||
location.pathname.match(/\/player\/series\/\d+/) ||
location.pathname.match(/\/player\/tours\/\d+/) ||
location.pathname.match(/\/player\/competitions\/\d+/) ||
location.pathname.match(/\/player\/games\/\d+\/play/) ||
// ... add new detail routes here
Why this matters:
PlayerLayout.tsxis the router wrapper with tabsisDetailViewtells it to hide tabs for detail pages- If your new detail page is NOT in
isDetailView, tabs will incorrectly show
Check if new route is missing:
- Look at the new page route (e.g.,
/player/documents/:docId) - Check if pattern exists in
isDetailView - If missing,
PlayerLayout.tsxneeds update
Step 4: Generate Violation Report
Create structured report:
## Navigation Structure Violations Report
### Total Violations: [count]
---
### Violation 1: Incorrect PlayerPageLayout Usage
**File:** `frontend/src/views/player/CompetitionsPage.tsx`
**Issue:** Top-level list page incorrectly uses PlayerPageLayout
**Severity:** High - Breaks navigation UX
**Current (❌ Wrong):**
import { PlayerPageLayout } from "@/components/layout/PlayerPageLayout"
export default function CompetitionsPage() { return ( <PlayerPageLayout title="All Competitions"> <CompetitionsList /> </PlayerPageLayout> ) }
**Fix (✅ Correct):**
export default function CompetitionsPage() { return ( <div className="min-h-screen bg-gradient-to-br from-scorecard to-rough"> <div className="container mx-auto px-4 py-8"> <h1 className="text-display-lg text-fairway mb-6">All Competitions</h1> <CompetitionsList /> </div> </div> ) }
**Explanation:** `/player/competitions` is a top-level list page, not a detail page. It should use plain layout, not PlayerPageLayout.
---
### Violation 2: Missing PlayerPageLayout on Detail Page
**File:** `frontend/src/views/player/TourDetailPage.tsx`
**Issue:** Detail page missing PlayerPageLayout
**Severity:** Medium - Inconsistent navigation
**Current (❌ Wrong):**
export default function TourDetailPage() { return ( <div className="container mx-auto"> <h1>{tour.name}</h1> <TourDetails tour={tour} /> </div> ) }
**Fix (✅ Correct):**
import { PlayerPageLayout } from "@/components/layout/PlayerPageLayout"
export default function TourDetailPage() { return ( <PlayerPageLayout title={tour.name} seriesId={tour.series_id} tourId={tour.id} showBackButton={true} > <TourDetails tour={tour} /> </PlayerPageLayout> ) }
**Explanation:** `/player/tours/:tourId` shows a single tour, so it's a detail page requiring PlayerPageLayout.
---
### Violation 3: Missing isDetailView Configuration
**File:** `frontend/src/views/player/PlayerLayout.tsx`
**Issue:** New detail route not in isDetailView
**Severity:** High - Tabs show on detail pages
**Current (❌ Missing):**
const isDetailView = location.pathname.match(/\/player\/series\/\d+/) || location.pathname.match(/\/player\/tours\/\d+/) || // New route missing
**Fix (✅ Add pattern):**
const isDetailView = location.pathname.match(/\/player\/series\/\d+/) || location.pathname.match(/\/player\/tours\/\d+/) || location.pathname.match(/\/player\/documents\/\d+/) || // Added
**Explanation:** New document detail page needs pattern in isDetailView to hide tabs.
---
### Summary
- **Total Issues:** [count]
- **High Severity:** [count]
- **Medium Severity:** [count]
**Action Required:** Fix violations to maintain consistent navigation UX.
**Approval needed:** Should I apply these fixes now?
Common Violation Patterns
Pattern 1: List Page Using PlayerPageLayout
Wrong:
// ❌ Bad: List page with PlayerPageLayout
export default function AllSeriesPage() {
return (
<PlayerPageLayout title="All Series">
<SeriesList />
</PlayerPageLayout>
)
}
Correct:
// ✅ Good: List page with plain layout
export default function AllSeriesPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-scorecard to-rough">
<div className="container mx-auto px-4 py-8">
<h1 className="text-display-lg text-fairway mb-6">All Series</h1>
<SeriesList />
</div>
</div>
)
}
Pattern 2: Detail Page Without PlayerPageLayout
Wrong:
// ❌ Bad: Detail page without PlayerPageLayout
export default function CompetitionDetailPage() {
return (
<div>
<h1>{competition.name}</h1>
<CompetitionDetails />
</div>
)
}
Correct:
// ✅ Good: Detail page with PlayerPageLayout
import { PlayerPageLayout } from "@/components/layout/PlayerPageLayout"
export default function CompetitionDetailPage() {
const { competitionId } = useParams()
const { data: competition } = useCompetition(competitionId)
return (
<PlayerPageLayout
title={competition.name}
seriesId={competition.series_id}
showBackButton={true}
>
<CompetitionDetails />
</PlayerPageLayout>
)
}
Pattern 3: New Route Not in isDetailView
When adding /player/statistics/:playerId:
Wrong (isDetailView unchanged):
// ❌ PlayerLayout.tsx unchanged - tabs will show on stats page
const isDetailView =
location.pathname.match(/\/player\/series\/\d+/) ||
location.pathname.match(/\/player\/tours\/\d+/)
// Missing statistics route
Correct (isDetailView updated):
// ✅ Add new route pattern
const isDetailView =
location.pathname.match(/\/player\/series\/\d+/) ||
location.pathname.match(/\/player\/tours\/\d+/) ||
location.pathname.match(/\/player\/statistics\/\d+/) // Added
PlayerPageLayout Props Reference
When using PlayerPageLayout, provide appropriate props:
<PlayerPageLayout
title="Page Title" // Required: Page heading
subtitle="Optional subtitle" // Optional: Secondary text
showBackButton={true} // Optional: Show back button (default: true)
seriesId={seriesId} // Optional: For breadcrumb context
seriesName="Series Name" // Optional: Override series display name
tourId={tourId} // Optional: For breadcrumb context
tourName="Tour Name" // Optional: Override tour display name
showHamburgerMenu={true} // Optional: Show hamburger menu (default: true)
customActions={<Button>...</Button>} // Optional: Custom header actions
>
{children}
</PlayerPageLayout>
Best practices:
- Always provide
title - Include
seriesIdif page is part of a series - Include
tourIdif page is part of a tour - Use
customActionsfor page-specific buttons (Edit, Delete, etc.)
Validation Scripts
Check All Pages for PlayerPageLayout Usage
# Find all pages using PlayerPageLayout
echo "=== Pages using PlayerPageLayout ==="
grep -r "PlayerPageLayout" frontend/src/views/player/*.tsx --include="*.tsx" -l
# Check each usage
for file in $(grep -r "PlayerPageLayout" frontend/src/views/player/*.tsx --include="*.tsx" -l); do
echo ""
echo "=== $file ==="
basename=$(basename "$file" .tsx)
# Determine if this should be a detail page based on filename
if [[ $basename == *"Detail"* ]] || [[ $basename == *"Profile"* ]]; then
echo "✅ Likely correct (detail page)"
else
echo "⚠️ Review needed (might be list page)"
echo "Route: Check router.tsx for this component"
fi
done
Check isDetailView Completeness
# Show current isDetailView patterns
echo "=== Current isDetailView patterns ==="
grep -A30 "isDetailView" frontend/src/views/player/PlayerLayout.tsx
echo ""
echo "=== All detail page routes ==="
# Find routes with parameters (likely detail pages)
grep -r "path.*:.*id" frontend/src/router.tsx
Feedback Loop
After generating report:
- Review violations with user
- Classify severity:
- High: List page with PlayerPageLayout OR detail page missing isDetailView - Medium: Detail page without PlayerPageLayout
- Ask: Should I fix these violations now?
- If yes: Apply fixes in order:
- Fix PlayerPageLayout usage first - Then update isDetailView in PlayerLayout.tsx
- Re-validate: Check all pages again
- Confirm: Navigation structure consistent
Decision Tree
Use this to determine correct layout:
Is the page showing a SINGLE entity (series, tour, competition, player)?
├─ YES → Use PlayerPageLayout
│ └─ Add route to isDetailView in PlayerLayout.tsx
└─ NO → Is it a LIST or COLLECTION?
└─ YES → DO NOT use PlayerPageLayout
└─ Use plain layout with gradient background
Examples:
/player/series/:id→ YES (single series) → Use PlayerPageLayout ✅/player/series→ NO (list of series) → Plain layout ✅/player/tours/:tourId/standings→ YES (single tour standings) → Use PlayerPageLayout ✅/player/profile→ YES (user's profile) → Use PlayerPageLayout ✅/player/competitions→ NO (list) → Plain layout ✅
Key Constraints
- PlayerPageLayout for detail pages ONLY (single entity context)
- Plain layout for list pages (collections, dashboards)
- Always update isDetailView when adding new detail routes
- Consistent back button behavior on detail pages
- Hamburger menu context must match page context (series/tour breadcrumbs)
Summary
Navigation validation ensures consistent UX, correct tab visibility, and proper breadcrumbs. Run when adding pages, modifying layouts, or before commit.