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
5 changes: 3 additions & 2 deletions .github/workflows/crawl-adk-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ name: Crawl ADK Documentation

on:
workflow_dispatch: # Manual trigger
schedule:
- cron: '0 0 * * 0' # Weekly on Sundays at midnight UTC
# schedule disabled — burns Actions minutes
# schedule:
# - cron: '0 0 * * 0' # Weekly on Sundays at midnight UTC

permissions:
contents: read
Expand Down
20 changes: 14 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ npm run ios # iOS simulator
npm run android # Android emulator
npx expo prebuild # Generate native projects
```
Expo SDK 54 + Expo Router (file-based routing) + NativeWind (Tailwind for RN) + React Query + MMKV/SecureStore for storage. Shares `firebase`, `zod`, `react-hook-form`, and `zustand` with web app.

### Testing
```bash
npm run test:unit # Vitest unit tests (src/**/*.test.ts)
npm run test:unit # Vitest unit tests
npm run test:watch # Vitest watch mode
npm run test:coverage # Coverage report (V8)
npm run test:integration # Integration tests (Firebase emulators required)
npm run test:integration:emulator # Spins up emulators + runs integration tests
npm run test:e2e # Playwright E2E (03-Tests/e2e/) on port 4000
npm run test:e2e:ui # Playwright UI mode (interactive)
npm run test:e2e:debug # Debug mode (PWDEBUG=1, headed, Chromium)
npm run test:security # npm audit (moderate+ severity)
npm run qa:e2e:smoke # Quick smoke tests (login + journey)
npm run qa:e2e:update-snapshots # Update visual regression baselines

Expand All @@ -57,7 +61,11 @@ npx vitest run src/lib/billing/plan-limits.test.ts
npx playwright test 03-Tests/e2e/01-authentication.spec.ts --project=chromium
```

**E2E details**: Tests run on port 4000 (not 3000). CI builds production app (standalone) then tests against it. Locally, dev server is used. Global setup (`03-Tests/e2e/global-setup.ts`) creates authenticated storage state reused across tests. Use `test:e2e:debug` to step through with Playwright Inspector.
**Unit tests**: Both co-located (`src/lib/**/*.test.ts`, `src/middleware.test.ts`) and in `src/__tests__/` (`src/__tests__/lib/`, `src/__tests__/api/`).

**Integration tests** (`*.integration.test.ts`): Separate vitest config (`vitest.integration.config.mts`), run against Firebase emulators in `node` environment with `forks` pool. Found in `src/lib/firebase/admin-services/` and `src/lib/`.

**E2E details**: Tests run on port 4000 (not 3000), overridable via `PLAYWRIGHT_BASE_URL`. CI builds production app (standalone) then tests against it. Locally, dev server is used. Global setup (`03-Tests/e2e/global-setup.ts`) creates authenticated storage state (`03-Tests/e2e/.auth/user.json`) reused across tests. Use `test:e2e:debug` to step through with Playwright Inspector.

### Firebase & Cloud Functions
```bash
Expand Down Expand Up @@ -178,7 +186,7 @@ Edge middleware protects `/dashboard/*` and `/api/*` routes:
- Protected API routes → return 401 JSON if no session
- Public API routes (no auth required): `/api/health`, `/api/auth/*`, `/api/waitlist`, `/api/webhooks`, `/api/verify`

Debug middleware with `MIDDLEWARE_DEBUG=verbose npm run dev`
Debug middleware with `npm run dev:debug` (sets `MIDDLEWARE_DEBUG=verbose`)

### Auth Domain & Email Links
- `authDomain` in client config (`NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN`) stays as `hustleapp-production.firebaseapp.com` — internal SDK setting, not user-facing. Changing to `hustlestats.io` would break OAuth.
Expand Down Expand Up @@ -216,9 +224,9 @@ Enforcement: server-side in `src/lib/stripe/plan-enforcement.ts` and `src/lib/wo

## Testing Strategy

- **Unit tests**: Vitest + Testing Library, co-located as `src/**/*.test.ts` (jsdom environment, `@vitejs/plugin-react`)
- **E2E tests**: Playwright in `03-Tests/e2e/`, numbered sequentially (01-authentication, 02-dashboard, etc.). Snapshots in `03-Tests/snapshots/`.
- **Firestore tests**: Use Firebase emulators locally
- **Unit tests**: Vitest + Testing Library (jsdom environment, `@vitejs/plugin-react`). Co-located in `src/lib/` and `src/__tests__/`.
- **Integration tests**: Vitest in `node` environment against Firebase emulators. Files: `*.integration.test.ts` in `src/lib/firebase/admin-services/` and `src/lib/`. Separate config: `vitest.integration.config.mts`.
- **E2E tests**: Playwright in `03-Tests/e2e/`, numbered sequentially (01-authentication, 02-dashboard, etc.). Snapshots in `03-Tests/snapshots/`. Sequential execution (single worker) for Firebase stability.
- **Visual regression**: Playwright screenshot comparison with 0.2% pixel tolerance

## Environment Variables
Expand Down
7 changes: 7 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ const eslintConfig = [
"functions/**",
// Mobile app (separate Expo project)
"mobile/**",
// Local Python env / agent tooling (not production JS/TS)
"vertex-agents/**",
// Test utility scripts
"03-Tests/scripts/**/*.js",
// Generated Playwright artifacts / reports
"03-Tests/playwright-report/**",
"03-Tests/results/**",
"03-Tests/test-results.json",
"test-results/**",
],
},
];
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/account/pin/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const logger = createLogger('api/account/pin');
*/
export async function PATCH(request: NextRequest) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/billing/create-checkout-session/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function POST(request: NextRequest) {
}

// 1. Authenticate user
const session = await auth();
const session = await auth(request);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/debug/biometrics/[playerId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function GET(
{ params }: { params: Promise<{ playerId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/debug/workout-logs/[playerId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function GET(
{ params }: { params: Promise<{ playerId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/games/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const RATE_LIMIT_MAX = 10; // 10 requests per minute
// GET /api/games?playerId=xxx - Get all games for a player
export async function GET(request: NextRequest) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -76,7 +76,7 @@ export async function GET(request: NextRequest) {
// POST /api/games - Create a new game log
export async function POST(request: NextRequest) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/players/[id]/assessments/[assessmentId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string; assessmentId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -90,7 +90,7 @@ export async function PUT(
{ params }: { params: Promise<{ id: string; assessmentId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -175,7 +175,7 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string; assessmentId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/assessments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -122,7 +122,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/players/[id]/biometrics/[logId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function PUT(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -144,7 +144,7 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/biometrics/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -128,7 +128,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/players/[id]/cardio-logs/[logId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -68,7 +68,7 @@ export async function PATCH(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -135,7 +135,7 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/cardio-logs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -96,7 +96,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/dream-gym/ai-strategy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -213,7 +213,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/players/[id]/dream-gym/check-in/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string; eventId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/players/[id]/dream-gym/events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/dream-gym/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -61,7 +61,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function PUT(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -137,7 +137,7 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string; logId: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/players/[id]/dream-gym/workout-logs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down Expand Up @@ -108,7 +108,7 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
const session = await auth(request);

if (!session?.user?.id) {
return NextResponse.json(
Expand Down
Loading
Loading