Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ htmlcov/
.tmp/
tmp/

# Playwright
test-results/
playwright-report/

# Misc artifacts
nul

# Claude local session data
.claude/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
104 changes: 88 additions & 16 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SmartShooter Roadmap (Execution Snapshot)

Last updated: 2026-02-15
Last updated: 2026-02-18 (Phase 1 Architecture Stabilization — ALL items complete)

## Current Goal
Ship a portfolio-grade analytics and training workflow with compact UX, stable role-gated dev tools, and clean PR-sized delivery.
Expand All @@ -27,27 +27,99 @@ Ship a portfolio-grade analytics and training workflow with compact UX, stable r
- unified action button style
- mobile actions are icon-only
- orange neon treatment in dark mode
11. Analytics comparator improvements:
- KPI deltas vs previous period
- fallback to `last active period` when immediate previous has no data
- source-aware labels (`vs prev` / `vs last active` / `n/a`)
- compare-details toggle with current/baseline ranges
12. Achievements UX depth:
- grouped achievement sections by category
- locked milestone previews in modal
13. Regression safety additions:
- tests for comparison fallback behavior
- tests for achievement grouping/locked preview mapping
- tests for mobile date overflow class guards

### In Progress
1. Product hardening around role assignment workflow and admin onboarding clarity
2. Final polish on analytics consistency across light/dark and empty-data states
1. Docs closure — keep roadmap and ux-spec synchronized after each merged PR

### Next (Priority Order)
1. Admin lifecycle polish
- Add UI hint for non-admin users when dev mode is unavailable
- Add explicit refresh-token guidance after role claim updates
2. Analytics comparator pass
- Add period-over-period KPI deltas
- Keep current compact layout constraints
3. Achievements UX depth
- Group achievements by category and recency
- Add locked milestone previews
4. Regression safety
- Add targeted tests for attempts-vs-made, milestone modal, and filters
- Add targeted tests for streak responsive defaults and compact mobile navbar behavior
5. Docs closure
1. ~~Firestore security rules~~ ✓ Done — `firestore.rules` + `firebase.json` created; sessions are owner-only, achievements subcollection is owner-only
2. ~~Admin lifecycle polish~~ ✓ Done — non-admin dev tools hint in Navbar; refresh-token note in dev tools dropdown
3. ~~Analytics comparator polish~~ ✓ Done — Auto/Prev/Last-active mode switch in KpiTiles; baseline range mini-label always visible beneath KPI tiles
4. ~~Milestones progression UX polish~~ ✓ Done — category completion counters (e.g. 1/3); next-tier preview row in milestone modal
5. ~~Regression safety expansion~~ ✓ Done — 13 new tests: `analytics.comparator.test.js` (6) + `catalog.milestone.test.js` (7); suite now 31/31
6. Docs closure
- Keep roadmap and ux-spec synchronized after each merged PR

### Completed (session 2026-02-18)
14. Firestore security rules — `firestore.rules` + `firebase.json` (sessions + achievements owner-only, default deny all)
15. Remove unused deps — `recharts`, `chart.js`, `framer-motion` removed; 42 packages uninstalled
16. Resolve XP source-of-truth — `src/services/xpService.js` deleted; XP lives on session documents via `xpCalculator.js`
17. Fix `LogSection.jsx` inert filter state — dead `typeFilter`/`from`/`to` state removed; filtering handled upstream
18. Admin lifecycle polish — non-admin dev tools visual hint; role-claim refresh guidance note
19. Analytics comparator polish — Auto/Prev/Last-active mode switch; always-visible baseline range label
20. Milestones progression UX polish — category counters (N/total) + next-tier preview row per category
21. Regression safety expansion — 13 new tests covering comparator modes and milestone modal states
22. Phase 1 item: Merge badge registries — `engine.js` imports from `definitions.js`; emoji icons added to definitions
23. Phase 1 item: Extract `useSessionData()` — `src/hooks/useSessionData.js`; Dashboard 373 → 155 lines
24. Phase 1 item: Extract `useAnalytics()` — `src/hooks/useAnalytics.js`; all analytics memos moved out of Dashboard
25. Phase 1 item: Extract `lib/insights.js` — pure `typeLabel` + `generateInsights`; InsightsSummaryCard is now presenter-only
26. Phase 1 item: JSDoc typedefs — `ZoneStat`, `Round`, `Session` added to `src/lib/models.js`
27. Phase 1 item: Vitest migration — `node:test` → Vitest; `SessionForm.test.jsx` (13 tests); suite 44/44
28. Phase 1 item: Playwright smoke test — `e2e/dashboard.spec.js` (3 tests); unauthenticated redirect flow
29. Bug fix: Dev tools dropdown text color in dark mode (`dark:text-neutral-100` added to all 3 buttons)

## Architecture Backlog

Technical debt and evolution items identified in architecture review (2026-02-18). Work these as dedicated refactor PRs alongside product features.

### Immediate (Before New Features)

All Immediate items shipped (2026-02-18):

- [x] **Firestore security rules** — `firestore.rules` + `firebase.json` created (tracked in Next above, now complete)
- [x] **Remove unused dependencies** — `recharts`, `chart.js`, `framer-motion` removed from `package.json`; 42 packages uninstalled
- [x] **Resolve XP source-of-truth** — `src/services/xpService.js` deleted; XP source of truth is session documents computed via `xpCalculator.js`
- [x] **Fix `LogSection.jsx` inert filter state** — dead `typeFilter`, `from`, `to` `useState` declarations and their dependent `useMemo` removed; filtering is handled upstream in `Dashboard.jsx`

### Phase 1 — Architecture Stabilization

Small, PR-sized refactors. No behavior changes. Each item is independently shippable.

- [x] **Merge badge registries** ✓ — `engine.js` now imports `BADGES` from `definitions.js`; inline duplicate removed. `definitions.js` has real emoji icons (🎯🔥🚀🏀).
- [x] **Extract `useSessionData()` hook** ✓ — `src/hooks/useSessionData.js` created; `Dashboard.jsx` reduced from 373 → ~155 lines.
- [x] **Extract `useAnalytics()` hook** ✓ — `src/hooks/useAnalytics.js` created; all 6 filter-derived `useMemo` blocks moved out of Dashboard.
- [x] **Extract `lib/insights.js`** ✓ — `src/lib/insights.js` created with `typeLabel()` + `generateInsights()`; `InsightsSummaryCard.jsx` is now a pure presenter (~12 lines).
- [x] **Add JSDoc `@typedef` for core models** ✓ — `ZoneStat`, `Round`, `Session` typedefs added at top of `src/lib/models.js`.
- [x] **Migrate test runner to Vitest** ✓ — `vitest.config.js` + `src/test/setup.js` created; all 7 test files migrated from `node:test` syntax; `SessionForm.test.jsx` added (13 component tests); suite 44/44.
- [x] **Add Playwright smoke test** ✓ — `playwright.config.js` + `e2e/dashboard.spec.js` created; 3 smoke tests (unauthenticated redirect flow); 3/3 pass.

### Phase 2 — AI Layer (Future)

- Define `InsightResult` schema in `src/lib/insights.js`: `{ type, severity, headline, detail, actionZone? }`
- Expand rule-based insight engine: plateau detection (last 3 sessions ±2%), training type imbalance, milestone proximity warnings
- Add `src/lib/aiService.js` with rule-engine fallback + LLM adapter interface — same output contract either way, frontend code unchanged

### Phase 3 — Multi-User & Coach Mode (Future)

- Design multi-user Firestore schema: `users/{uid}`, `teams/{teamId}`, `teams/{teamId}/members/{uid}`
- Add `teamId` field to session documents (nullable, backwards compatible)
- Coach manages multiple players, player comparison analytics, shared drill templates
- Update Firestore security rules for team-scoped access

### Phase 4 — Production Readiness (Future)

- Backend API + persistent DB layer (evaluate Firestore → Postgres migration if coach-mode cross-player queries demand it)
- Error monitoring (Sentry or equivalent)
- Performance optimization + privacy-first hosting
- Analytics pipeline + monetization / subscription layer (Stripe + Firebase Functions for subscription gating via role claims)

## Next Session Start Point
1. **Phase 1 Architecture Stabilization is 100% complete** (all 7 items shipped).
2. Suite: **44/44 unit+component tests pass** (`npm test`). **3/3 E2E smoke tests pass** (`npm run test:e2e`). Lint clean. Build clean.
3. Next: **Phase 2 AI Layer** — expand `src/lib/insights.js` with rule-based engine (plateau detection, type imbalance, milestone proximity), then add `src/lib/aiService.js` with LLM adapter interface.

## PR Workflow Rules
1. Keep changes PR-sized and reviewable.
2. For each PR:
Expand Down
26 changes: 26 additions & 0 deletions e2e/dashboard.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect } from '@playwright/test'

test.describe('Unauthenticated flow smoke test', () => {
test('navigating to / redirects to /login and renders sign-in UI', async ({ page }) => {
await page.goto('/')
await page.waitForURL('**/login', { timeout: 10_000 })
expect(page.url()).toContain('/login')
await expect(page.getByText('SmartShooter')).toBeVisible()
await expect(page.getByRole('button', { name: /continue with google/i })).toBeVisible()
await expect(page.getByPlaceholder('Email')).toBeVisible()
await expect(page.getByRole('button', { name: /sign in/i })).toBeVisible()
})

test('navigating directly to /dashboard redirects to /login', async ({ page }) => {
await page.goto('/dashboard')
await page.waitForURL('**/login', { timeout: 10_000 })
expect(page.url()).toContain('/login')
await expect(page.getByPlaceholder('Email')).toBeVisible()
})

test('page title is smartshooter', async ({ page }) => {
await page.goto('/')
await page.waitForURL('**/login', { timeout: 10_000 })
await expect(page).toHaveTitle('smartshooter')
})
})
7 changes: 7 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
// Node environment for config files and e2e tests
{
files: ['*.config.js', 'vitest.config.js', 'e2e/**/*.{js,ts}'],
languageOptions: {
globals: { ...globals.node },
},
},
{
files: ['**/*.{js,jsx}'],
extends: [
Expand Down
5 changes: 5 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"firestore": {
"rules": "firestore.rules"
}
}
42 changes: 42 additions & 0 deletions firestore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
rules_version = '2';

service cloud.firestore {
match /databases/{database}/documents {

// Sessions are readable and writable only by their owner.
// userId must match the authenticated user on both reads and writes.
match /sessions/{sessionId} {
allow read: if request.auth != null
&& request.auth.uid == resource.data.userId;

allow create: if request.auth != null
&& request.auth.uid == request.resource.data.userId;

allow update: if request.auth != null
&& request.auth.uid == resource.data.userId
&& request.auth.uid == request.resource.data.userId;

allow delete: if request.auth != null
&& request.auth.uid == resource.data.userId;
}

// Each user's achievements subcollection is accessible only by that user.
match /users/{uid}/achievements/{achievementId} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}

// The users/{uid} document (XP, level, profile) is accessible only by
// that user. Currently written only by client-side logic; rules are in
// place for future use.
match /users/{uid} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}

// Deny everything else by default (implicit, but made explicit for clarity).
match /{document=**} {
allow read, write: if false;
}
}
}
Loading