Validates correct usage of PlayerPageLayout component in TapScore. Ensures detail pages use PlayerPageLayout while top-level list pages do not...
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.
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
Detail pages show a single entity with specific context:
Examples:
/player/series/:seriesId/player/tours/:tourId/player/competitions/:competitionId/player/players/:playerId/player/profile/player/games/new/player/games/:gameId/play/player/competitions/:competitionId/tee-times/:teeTimeId/player/series/:seriesId/documents/:docIdPattern: URL includes dynamic ID parameter (:id, :seriesId, etc.)
List pages show collections without specific entity context:
Examples:
/player/player/competitions/player/series/player/tours/player/roundsPattern: URL is static (no dynamic ID parameters)
Check file imports:
grep -n "PlayerPageLayout" [file_path]
Check component usage:
grep -A5 "<PlayerPageLayout" [file_path]
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>
)
}
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.tsx is the router wrapper with tabsisDetailView tells it to hide tabs for detail pagesisDetailView, tabs will incorrectly showCheck if new route is missing:
/player/documents/:docId)isDetailViewPlayerLayout.tsx needs updateCreate 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):**
```tsx
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.
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.
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.
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:**
```tsx
// ā 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>
)
}
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>
)
}
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
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:
titleseriesId if page is part of a seriestourId if page is part of a tourcustomActions for page-specific buttons (Edit, Delete, etc.)# 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
# 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
After generating report:
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 ā
Navigation validation ensures consistent UX, correct tab visibility, and proper breadcrumbs. Run when adding pages, modifying layouts, or before commit.