# HustleStack - Technical Requirements
> Comprehensive technical specification for the HustleStack career development platform.
---
## 1. Stack Overview
| Category | Technology | Version | Purpose |
|----------|-----------|---------|---------|
| **Framework** | Next.js | 16+ | Full-stack React framework with App Router |
| **Runtime** | React | 19+ | UI library with concurrent features |
| **Compiler** | React Compiler | experimental | Automatic memoization and optimization |
| **Database** | Convex | latest | Real-time reactive database |
| **Authentication** | Clerk | latest | Auth with billing/subscriptions |
| **Payments** | Stripe | latest | Payment processing |
| **Email** | Resend | latest | Transactional email |
| **Styling** | Tailwind CSS | 4.x | Utility-first CSS |
| **UI Components** | shadcn/ui | latest | Accessible component primitives |
| **Icons** | react-icons (Phosphor) | latest | Lightweight icon library |
| **Analytics** | PostHog | latest | Product analytics |
| **Error Tracking** | Sentry | latest | Error monitoring |
| **Hosting** | Vercel | - | Edge deployment |
| **Package Manager** | Bun | 1.3+ | Fast JS runtime & package manager |
---
## 2. Technology Configuration
### 2.1 Next.js 16+
**Purpose**: Full-stack React framework with App Router, Server Components, and edge runtime support.
**Configuration** (`next.config.ts`):
```typescript
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
ppr: true, // Partial Pre-rendering
},
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'img.clerk.com' },
{ protocol: 'https', hostname: '*.convex.cloud' },
],
},
// Turbopack for development
turbopack: {},
};
export default nextConfig;
```
**Requirements**:
- App Router architecture (no Pages Router)
- Server Components by default
- Client Components only when necessary (`'use client'`)
- Route Groups for layout organization
- Parallel and Intercepting routes for modals
- Metadata API for SEO
---
### 2.2 React 19+
**Purpose**: UI library with concurrent rendering, Server Components, and improved hooks.
**Key Features to Use**:
- Server Components (default)
- `use` hook for promises and context
- `useOptimistic` for optimistic updates
- `useFormStatus` for form states
- `useActionState` for server actions
- Actions with `useTransition`
**Requirements**:
- Strict Mode enabled
- No legacy lifecycle methods
- Functional components only
---
### 2.3 React Compiler (Experimental)
**Purpose**: Automatic memoization eliminating need for manual `useMemo`, `useCallback`, `React.memo`.
**Configuration** (`babel.config.js` or via Next.js):
```javascript
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
target: '19',
}],
],
};
```
**Requirements**:
- Remove existing manual memoization
- Follow React Compiler rules (no rule-breaking patterns)
- Enable eslint-plugin-react-compiler
---
### 2.4 Tailwind CSS v4
**Purpose**: Utility-first CSS framework with native CSS variables and improved performance.
**Configuration** (`app/globals.css`):
```css
@import "tailwindcss";
@import "tw-animate-css";
@theme {
/* HustleStack Brand Colors */
--color-primary: oklch(0.65 0.19 250);
--color-primary-foreground: oklch(0.98 0.01 250);
--color-secondary: oklch(0.55 0.15 160);
--color-accent: oklch(0.75 0.18 45);
/* Semantic Colors */
--color-background: oklch(0.99 0.005 250);
--color-foreground: oklch(0.15 0.01 250);
--color-muted: oklch(0.95 0.01 250);
--color-muted-foreground: oklch(0.45 0.02 250);
/* Status Colors */
--color-success: oklch(0.65 0.18 145);
--color-warning: oklch(0.75 0.16 65);
--color-error: oklch(0.60 0.20 25);
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* Fonts */
--font-sans: 'Inter Variable', system-ui, sans-serif;
--font-mono: 'JetBrains Mono Variable', monospace;
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground antialiased;
}
}
```
**Requirements**:
- Use CSS-first configuration (no tailwind.config.js)
- OKLCH color space for all colors
- CSS variables for theming
- Dark mode via `@media (prefers-color-scheme: dark)` or class strategy
---
### 2.5 Clerk (Authentication + Billing)
**Purpose**: Complete authentication solution with built-in billing/subscriptions support.
**Configuration**:
```typescript
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)',
'/pricing',
'/about',
]);
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
```
**Billing Setup**:
```typescript
// Clerk Billing with Stripe
import { auth } from '@clerk/nextjs/server';
export async function getSubscriptionStatus() {
const { userId, orgId } = await auth();
// Use Clerk's billing features
}
```
**Requirements**:
- Clerk Core 2 SDK
- Billing add-on enabled
- Multi-tenant support for teams
- OAuth providers: Google, LinkedIn, GitHub
- Webhook handling for subscription events
---
### 2.6 Convex (Real-time Database)
**Purpose**: Reactive database with real-time sync, TypeScript-first schema, and serverless functions.
**Schema** (`convex/schema.ts`):
```typescript
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';
export default defineSchema({
users: defineTable({
clerkId: v.string(),
email: v.string(),
name: v.string(),
imageUrl: v.optional(v.string()),
tier: v.union(v.literal('free'), v.literal('pro'), v.literal('premium')),
stripeCustomerId: v.optional(v.string()),
createdAt: v.number(),
})
.index('by_clerk_id', ['clerkId'])
.index('by_email', ['email']),
profiles: defineTable({
userId: v.id('users'),
headline: v.optional(v.string()),
bio: v.optional(v.string()),
skills: v.array(v.string()),
experience: v.array(v.object({
title: v.string(),
company: v.string(),
startDate: v.string(),
endDate: v.optional(v.string()),
current: v.boolean(),
})),
goals: v.array(v.string()),
valueScore: v.optional(v.number()),
}).index('by_user', ['userId']),
roadmaps: defineTable({
userId: v.id('users'),
title: v.string(),
milestones: v.array(v.object({
id: v.string(),
title: v.string(),
completed: v.boolean(),
dueDate: v.optional(v.string()),
})),
createdAt: v.number(),
updatedAt: v.number(),
}).index('by_user', ['userId']),
});
```
**Client Setup**:
```typescript
// convex/convex.ts
'use client';
import { ConvexProvider, ConvexReactClient } from 'convex/react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { ClerkProvider, useAuth } from '@clerk/nextjs';
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**Requirements**:
- TypeScript-first schema definitions
- Optimistic updates for UX
- Real-time subscriptions for live data
- Server-side queries for initial page loads
- Proper indexing for all query patterns
---
### 2.7 Stripe (Payments)
**Purpose**: Payment processing for subscriptions and one-time purchases.
**Integration with Clerk**:
Clerk Billing handles the primary Stripe integration. Direct Stripe SDK used for:
- Webhook handling
- Custom checkout flows
- Refund processing
- Invoice management
**Webhook Handler**:
```typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text();
const sig = (await headers()).get('stripe-signature')!;
const event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'customer.subscription.updated':
case 'customer.subscription.deleted':
// Sync with Convex
break;
case 'invoice.payment_failed':
// Handle failed payment
break;
}
return Response.json({ received: true });
}
```
**Requirements**:
- Stripe SDK v14+
- Webhook signature verification
- Idempotent event handling
- Subscription lifecycle management
- PCI DSS compliance (Stripe handles card data)
---
### 2.8 Resend (Email)
**Purpose**: Transactional email for notifications, onboarding, and marketing.
**Configuration**:
```typescript
// lib/email.ts
import { Resend } from 'resend';
export const resend = new Resend(process.env.RESEND_API_KEY);
// Email templates as React components
import { WelcomeEmail } from '@/emails/welcome';
export async function sendWelcomeEmail(email: string, name: string) {
await resend.emails.send({
from: 'HustleStack ',
to: email,
subject: 'Welcome to HustleStack - Your potential is hustlestackped!',
react: WelcomeEmail({ name }),
});
}
```
**Email Types**:
- Welcome / Onboarding sequence
- Value assessment results
- Roadmap reminders
- Milestone achievements
- Mentor session confirmations
- Weekly progress digests
- Subscription updates
**Requirements**:
- React Email for templates
- Domain verification (hustlestack)
- Unsubscribe handling
- Email preferences in user settings
- Bounce/complaint handling
---
### 2.9 Icons (react-icons + Phosphor Light)
**Purpose**: Lightweight, consistent iconography.
**Usage**:
```typescript
// ALWAYS use Light weight only
import { PiRocketLight, PiUserLight, PiChartLineUpLight } from 'react-icons/pi';
// Never use other weights
// ❌ PiRocket, PiRocketBold, PiRocketFill, PiRocketDuotone
// ✅ PiRocketLight
```
**Icon Categories**:
- Navigation: `PiHouseLight`, `PiUserLight`, `PiGearLight`
- Actions: `PiPlusLight`, `PiPencilLight`, `PiTrashLight`
- Career: `PiBriefcaseLight`, `PiGraduationCapLight`, `PiTrophyLight`
- Status: `PiCheckCircleLight`, `PiWarningLight`, `PiInfoLight`
**Requirements**:
- ONLY Phosphor Light weight (`PiXxxLight`)
- Tree-shaking enabled (import specific icons)
- Consistent sizing via className
---
### 2.10 shadcn/ui Components
**Purpose**: Accessible, customizable component primitives built on Radix UI.
**Installation**:
```bash
bunx shadcn@latest init
bunx shadcn@latest add button card dialog form input
```
**Required Components**:
- `Button` - Primary actions
- `Card` - Content containers
- `Dialog` - Modals
- `Form` - React Hook Form integration
- `Input` - Text inputs
- `Select` - Dropdowns
- `Tabs` - Tab navigation
- `Toast` - Notifications
- `Avatar` - User images
- `Badge` - Status indicators
- `Progress` - Progress bars
- `Skeleton` - Loading states
- `Sheet` - Side panels
- `Command` - Command palette
**Requirements**:
- Tailwind CSS v4 compatible
- ARIA compliant
- Keyboard navigation
- Focus management
---
### 2.11 PostHog (Analytics)
**Purpose**: Product analytics, feature flags, session replay.
**Configuration**:
```typescript
// app/providers.tsx
'use client';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
if (typeof window !== 'undefined') {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false, // Manual with App Router
});
}
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
return {children};
}
```
**Event Tracking**:
```typescript
import { usePostHog } from 'posthog-js/react';
function ValueAssessment() {
const posthog = usePostHog();
const handleComplete = (score: number) => {
posthog.capture('value_assessment_completed', {
score,
tier: user.tier,
});
};
}
```
**Requirements**:
- EU hosting for GDPR compliance
- Session replay enabled
- Feature flags for A/B testing
- User identification via Clerk ID
- Custom events for key actions
---
### 2.12 Sentry (Error Tracking)
**Purpose**: Error monitoring, performance tracking, and alerting.
**Configuration**:
```typescript
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: false,
}),
],
});
```
**Requirements**:
- Source maps uploaded on build
- User context from Clerk
- Performance monitoring enabled
- Release tracking
- Alert rules for critical errors
---
## 3. Environment Variables
### Required Variables
```bash
# .env.local
# ─────────────────────────────────────────────────────────────
# Core Application
# ─────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=http://localhost:3000
# ─────────────────────────────────────────────────────────────
# Convex
# ─────────────────────────────────────────────────────────────
CONVEX_DEPLOYMENT=dev:hustlestack-us
NEXT_PUBLIC_CONVEX_URL=https://xxx.convex.cloud
# ─────────────────────────────────────────────────────────────
# Clerk Authentication
# ─────────────────────────────────────────────────────────────
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
CLERK_SECRET_KEY=sk_test_xxx
CLERK_WEBHOOK_SECRET=whsec_xxx
# Sign-in/up redirect URLs
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
# ─────────────────────────────────────────────────────────────
# Stripe
# ─────────────────────────────────────────────────────────────
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
# Price IDs
STRIPE_PRO_MONTHLY_PRICE_ID=price_xxx
STRIPE_PRO_YEARLY_PRICE_ID=price_xxx
STRIPE_PREMIUM_MONTHLY_PRICE_ID=price_xxx
STRIPE_PREMIUM_YEARLY_PRICE_ID=price_xxx
# ─────────────────────────────────────────────────────────────
# Resend
# ─────────────────────────────────────────────────────────────
RESEND_API_KEY=re_xxx
# ─────────────────────────────────────────────────────────────
# PostHog
# ─────────────────────────────────────────────────────────────
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx
NEXT_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
# ─────────────────────────────────────────────────────────────
# Sentry
# ─────────────────────────────────────────────────────────────
NEXT_PUBLIC_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
SENTRY_AUTH_TOKEN=sntrys_xxx
SENTRY_ORG=hustlestack
SENTRY_PROJECT=hustlestack-us
```
### Environment Variable Validation
```typescript
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
// Convex
NEXT_PUBLIC_CONVEX_URL: z.string().url(),
// Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().startsWith('pk_'),
CLERK_SECRET_KEY: z.string().startsWith('sk_'),
// Stripe
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
// Resend
RESEND_API_KEY: z.string().startsWith('re_'),
// PostHog
NEXT_PUBLIC_POSTHOG_KEY: z.string().startsWith('phc_'),
// Sentry
NEXT_PUBLIC_SENTRY_DSN: z.string().url(),
});
export const env = envSchema.parse(process.env);
```
---
## 4. Development Setup
### Prerequisites
- **Bun** v1.3.1+ (package manager & runtime)
- **Node.js** 22+ (for tooling compatibility)
- **Git** with SSH key configured
- **VS Code** with recommended extensions
### Initial Setup
```bash
# Clone repository
git clone git@github.com:hustlestack-us/hustlestack.git
cd hustlestack
# Install dependencies
bun install
# Set up environment
cp .env.example .env.local
# Edit .env.local with your credentials
# Set up Convex
bunx convex dev
# Run development server (separate terminal)
bun run dev
# Open http://localhost:3000
```
### VS Code Extensions (Required)
```json
// .vscode/extensions.json
{
"recommendations": [
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"prisma.prisma",
"ms-vscode.vscode-typescript-next",
"unifiedjs.vscode-mdx",
"streetsidesoftware.code-spell-checker"
]
}
```
### Scripts
```json
// package.json scripts
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint && tsc --noEmit",
"format": "prettier --write .",
"test": "vitest",
"test:e2e": "playwright test",
"convex:dev": "convex dev",
"convex:deploy": "convex deploy",
"db:push": "convex deploy",
"email:dev": "email dev --port 3001"
}
}
```
---
## 5. Build & Deployment
### Build Process
```bash
# Production build
bun run build
# Build outputs:
# - .next/static: Static assets (cached forever)
# - .next/server: Server components and API routes
# - .vercel/output: Vercel deployment artifact
```
### Vercel Configuration
```json
// vercel.json
{
"framework": "nextjs",
"buildCommand": "bun run build",
"installCommand": "bun install",
"regions": ["iad1"],
"crons": [
{
"path": "/api/cron/weekly-digest",
"schedule": "0 9 * * 1"
}
]
}
```
### Deployment Checklist
1. **Environment Variables**: All production keys configured in Vercel
2. **Convex**: Production deployment active (`convex deploy`)
3. **Clerk**: Production instance with correct URLs
4. **Stripe**: Live mode webhooks configured
5. **Resend**: Domain verified
6. **PostHog**: Production project
7. **Sentry**: Release tracking enabled
### CI/CD Pipeline
```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run test
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
```
---
## 6. Performance Requirements
### Core Web Vitals Targets
| Metric | Target | Maximum |
|--------|--------|---------|
| **LCP** (Largest Contentful Paint) | < 1.5s | < 2.5s |
| **INP** (Interaction to Next Paint) | < 100ms | < 200ms |
| **CLS** (Cumulative Layout Shift) | < 0.05 | < 0.1 |
| **FCP** (First Contentful Paint) | < 1.0s | < 1.8s |
| **TTFB** (Time to First Byte) | < 200ms | < 500ms |
### Performance Strategies
```typescript
// 1. Server Components (default)
// Render on server, zero JS for static content
export default async function Dashboard() {
const data = await fetchData();
return ;
}
// 2. Streaming with Suspense
import { Suspense } from 'react';
export default function Page() {
return (
<>
{/* Instant */}
}>
{/* Streams when ready */}
>
);
}
// 3. Optimistic Updates
import { useOptimistic } from 'react';
function TodoList({ todos }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
}
// 4. Image Optimization
import Image from 'next/image';
```
### Bundle Size Limits
| Chunk | Target | Maximum |
|-------|--------|---------|
| Initial JS | < 100KB | < 150KB |
| Per-route JS | < 50KB | < 100KB |
| Total CSS | < 30KB | < 50KB |
| Third-party | < 75KB | < 100KB |
### Monitoring
- **Vercel Analytics**: Real-user Core Web Vitals
- **Sentry Performance**: Transaction tracing
- **PostHog**: Feature performance correlation
---
## 7. Security Requirements
### Authentication & Authorization
```typescript
// Server-side auth check
import { auth } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';
export default async function ProtectedPage() {
const { userId } = await auth();
if (!userId) redirect('/sign-in');
// ...
}
// API route protection
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// ...
}
```
### Data Validation
```typescript
// Zod schemas for all inputs
import { z } from 'zod';
export const profileSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
bio: z.string().max(500).optional(),
skills: z.array(z.string()).max(20),
});
// Convex validators
import { v } from 'convex/values';
export const updateProfile = mutation({
args: {
name: v.string(),
bio: v.optional(v.string()),
},
handler: async (ctx, args) => {
// Validated args
},
});
```
### Security Headers
```typescript
// next.config.ts
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
];
```
### Rate Limiting
```typescript
// API rate limiting via Vercel Edge Config or Upstash
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'),
});
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
// ...
}
```
### Compliance Requirements
- **GDPR**: EU user data handling, consent, data export/deletion
- **CCPA**: California privacy rights
- **SOC 2**: Via Clerk, Convex, Stripe compliance
- **PCI DSS**: Stripe handles payment card data
---
## 8. Testing Requirements
### Testing Stack
| Type | Tool | Coverage Target |
|------|------|-----------------|
| Unit | Vitest | 80% |
| Integration | Vitest + Testing Library | 70% |
| E2E | Playwright | Critical paths |
| Visual | Playwright screenshots | Key pages |
### Unit Testing
```typescript
// lib/__tests__/value-calculator.test.ts
import { describe, it, expect } from 'vitest';
import { calculateValueScore } from '../value-calculator';
describe('calculateValueScore', () => {
it('returns correct score for junior developer', () => {
const score = calculateValueScore({
yearsExperience: 2,
skills: ['React', 'TypeScript'],
education: 'bachelors',
});
expect(score).toBeGreaterThan(50);
expect(score).toBeLessThan(75);
});
});
```
### Component Testing
```typescript
// components/__tests__/value-card.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ValueCard } from '../value-card';
describe('ValueCard', () => {
it('displays the value score', () => {
render();
expect(screen.getByText('85')).toBeInTheDocument();
expect(screen.getByText('Your Value Score')).toBeInTheDocument();
});
});
```
### E2E Testing
```typescript
// e2e/onboarding.spec.ts
import { test, expect } from '@playwright/test';
test('complete onboarding flow', async ({ page }) => {
await page.goto('/sign-up');
// Sign up with Clerk test credentials
await page.fill('[name="email"]', 'test@example.com');
await page.click('button[type="submit"]');
// Complete onboarding
await expect(page).toHaveURL('/onboarding');
await page.fill('[name="headline"]', 'Software Engineer');
await page.click('text=Continue');
// Verify dashboard
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Welcome');
});
```
### Convex Testing
```typescript
// convex/__tests__/users.test.ts
import { convexTest } from 'convex-test';
import { describe, it, expect } from 'vitest';
import schema from '../schema';
import { api } from '../_generated/api';
describe('users', () => {
it('creates a user correctly', async () => {
const t = convexTest(schema);
const userId = await t.mutation(api.users.create, {
clerkId: 'user_123',
email: 'test@example.com',
name: 'Test User',
});
const user = await t.query(api.users.get, { id: userId });
expect(user?.email).toBe('test@example.com');
});
});
```
### CI Testing
```yaml
# Run on all PRs
- run: bun run test
- run: bun run test:e2e
- run: bun run lint
- run: tsc --noEmit
```
---
## Appendix: Project Structure
```
hustlestack/
├── app/
│ ├── (auth)/
│ │ ├── sign-in/[[...sign-in]]/
│ │ └── sign-up/[[...sign-up]]/
│ ├── (marketing)/
│ │ ├── page.tsx # Landing page
│ │ ├── pricing/
│ │ └── about/
│ ├── (app)/
│ │ ├── dashboard/
│ │ ├── profile/
│ │ ├── roadmap/
│ │ ├── mentors/
│ │ └── settings/
│ ├── api/
│ │ └── webhooks/
│ │ ├── clerk/
│ │ └── stripe/
│ ├── layout.tsx
│ ├── globals.css
│ └── providers.tsx
├── components/
│ ├── ui/ # shadcn/ui components
│ ├── forms/
│ ├── cards/
│ └── layouts/
├── convex/
│ ├── schema.ts
│ ├── users.ts
│ ├── profiles.ts
│ ├── roadmaps.ts
│ └── _generated/
├── lib/
│ ├── env.ts
│ ├── utils.ts
│ └── email.ts
├── emails/
│ ├── welcome.tsx
│ └── weekly-digest.tsx
├── public/
├── e2e/
└── types/
```
---
*Last updated: 2026-02-07*
*Version: 1.0.0*