Skip to main content
CharleOS uses Next.js 16 App Router with a feature-based organization. This guide explains how the codebase is structured and where to find (or add) code.

Overview

charle-os/
├── app/                      # Next.js App Router (pages & routes)
├── components/               # React components (feature-based)
├── lib/                      # Shared utilities & business logic
├── hooks/                    # Custom React hooks
├── __tests__/                # Unit tests (Vitest)
├── e2e/                      # E2E tests (Playwright)
├── drizzle/                  # Database migrations
├── trigger/                  # Background jobs (Trigger.dev)
├── emails/                   # Email templates (React Email)
├── scripts/                  # Utility scripts
└── public/                   # Static assets

App Directory (/app)

The app/ directory contains all routes and pages using Next.js App Router.

Route Groups

CharleOS uses route groups to organize pages without affecting URLs:

(dashboard)/ - Main App

The authenticated team member application:
app/(dashboard)/
├── layout.tsx               # Dashboard layout (sidebar, header)
├── page.tsx                 # Dashboard home (/dashboard)
├── clients/                 # Client management
│   ├── [id]/               # Client detail page
│   └── page.tsx            # Client list
├── tasks/                   # Task management
├── quotes/                  # Quote management
├── projects/                # Project management
├── schedule/                # Schedule & calendar
├── help-desk/               # Support tickets
├── admin/                   # Admin settings
└── ...
Key features:
  • Shared layout with sidebar navigation
  • Role-based access control
  • Real-time updates via Pusher

client-portal/ - Client Portal

Separate app for clients to view their work:
app/client-portal/
├── (authenticated)/        # After sign-in
│   ├── quotes/            # View quotes
│   ├── tasks/             # View tasks
│   ├── help-desk/         # Submit tickets
│   └── roadmap/           # Project roadmap
├── sign-in/               # Client login
├── invite/                # Client invitation flow
└── layout.tsx             # Client portal layout
Separate from main app:
  • Different authentication (Better Auth with client context)
  • Different branding and navigation
  • Restricted to client-facing features only

(auth)/ - Authentication Pages

Team member authentication flow:
app/(auth)/
├── auth/
│   └── route.tsx          # Better Auth API handler
├── pending-approval/       # Pending role screen
└── layout.tsx             # Minimal auth layout

api/ - API Routes

All backend API endpoints:
app/api/
├── dashboard/              # Dashboard data
│   ├── day-rates/
│   ├── utilisation/
│   └── ...
├── clients/                # Client CRUD
├── tasks/                  # Task CRUD
├── quotes/                 # Quote CRUD
├── schedule/               # Schedule management
├── time-entries/           # Time tracking
├── admin/                  # Admin operations
└── auth/                   # Better Auth endpoints
Convention:
  • Each route exports GET, POST, PUT, PATCH, DELETE handlers
  • Use lib/services/ for business logic (keep routes thin)
  • Return structured responses with proper error handling

Special Files

FilePurpose
layout.tsxShared UI wrapper for child routes
page.tsxRoute content (actual page)
loading.tsxLoading state (Suspense fallback)
error.tsxError boundary
not-found.tsx404 page
route.tsAPI endpoint handler

Components Directory (/components)

All React components organized by feature:

Feature-Based Organization

components/
├── ui/                      # shadcn/ui base components
│   ├── button.tsx
│   ├── input.tsx
│   ├── dialog.tsx
│   └── ...                 # 100+ UI primitives
├── tasks/                   # Task-specific components
│   ├── task-card.tsx
│   ├── task-filters.tsx
│   └── wizard/             # Task creation wizard
├── quotes/                  # Quote components
├── calendar/                # Calendar & schedule
├── dashboard/               # Dashboard widgets
├── data-table/              # Reusable table components
├── badge/                   # Status badges
├── client-portal/           # Client portal components
└── ...

Component Naming

  • PascalCase for components: TaskCard.tsx
  • Feature prefixes for clarity: TaskStatusBadge, QuoteApprovalCard
  • Index files for clean imports: components/tasks/index.ts

UI Components (/components/ui)

Base components from shadcn/ui:
  • Built on Radix UI primitives
  • Styled with Tailwind CSS
  • Fully customizable
  • Don’t edit directly - use composition
Example usage:
import { Button } from "@/components/ui/button";
import { Dialog } from "@/components/ui/dialog";

Lib Directory (/lib)

Shared utilities, business logic, and configuration:

Services (/lib/services)

Business logic layer - keeps API routes thin:
lib/services/
├── tasks.ts                 # Task operations
├── quotes.ts                # Quote operations
├── clients.ts               # Client operations
├── metrics.ts               # Metric calculations
├── billing.ts               # Billing logic
├── schedule-calculator.ts   # Schedule generation
└── ...
Pattern:
  • Functions accept validated input
  • Return typed results or throw errors
  • Used by API routes and server components
Example:
// lib/services/tasks.ts
export async function createTask(data: CreateTaskInput) {
  // Validate, calculate, insert
  return task;
}

// app/api/tasks/route.ts
export async function POST(req: Request) {
  const data = await req.json();
  const task = await createTask(data);
  return Response.json(task);
}

Database (/lib/db)

Database schema and client:
lib/db/
├── schema.ts                # Drizzle schema (all tables)
├── index.ts                 # Database client
└── doc-chunks.ts            # RAG document chunks
Key files:
  • schema.ts - Single source of truth for database structure
  • Types auto-generated from schema
  • All tables, relations, enums defined here

Utils (/lib/utils)

Utility functions grouped by purpose:
lib/utils/
├── billing.ts               # Billing formula (MIN/MAX)
├── tshirt-sizes.ts          # T-shirt size calculations
├── day-rate.ts              # Day rate calculations
├── prorated-revenue.ts      # Revenue proration
├── working-days.ts          # Working day calculations
├── date-formatting.ts       # Date utilities
└── ...

Constants (/lib/constants)

Configuration and constants:
lib/constants/
├── tshirt-sizes.ts          # T-shirt size definitions
├── capacity.ts              # Capacity constants
├── status.ts                # Status enums & labels
├── target-metrics.ts        # Target thresholds
└── ...

Schemas (/lib/schemas)

Zod validation schemas for forms and API:
lib/schemas/
├── task.ts                  # Task validation
├── quote.ts                 # Quote validation
├── client.ts                # Client validation
└── ...
Usage:
import { createTaskSchema } from "@/lib/schemas/task";

const parsed = createTaskSchema.parse(formData);

AI (/lib/ai)

AI features (Alan assistant):
lib/ai/
├── agents/                  # AI agents
│   ├── general.ts          # General queries
│   ├── schedule.ts         # Schedule management
│   └── instructions/       # System prompts
├── tools/                   # AI tool functions
│   ├── tasks.ts
│   ├── schedule.ts
│   └── ...
└── rag.ts                   # RAG (doc search)

Hooks Directory (/hooks)

Custom React hooks for data fetching and state:
hooks/
├── use-task-detail.ts       # Fetch task by ID
├── use-task-list.ts         # Fetch tasks with filters
├── use-client-detail.ts     # Fetch client by ID
└── ...
Pattern (SWR-based):
export function useTaskDetail(id: string) {
  return useSWR(`/api/tasks/${id}`, fetcher, {
    // Optimistic updates, revalidation, etc.
  });
}

Tests

Unit Tests (/__tests__)

Vitest tests for utilities and logic:
__tests__/
├── billing.test.ts          # Billing formula tests
├── tshirt-sizes.test.ts     # T-shirt size calculations
├── working-days.test.ts     # Date utilities
└── ...
Run tests:
npm run test          # Run once
npm run test:watch    # Watch mode

E2E Tests (/e2e)

Playwright tests for critical flows:
e2e/
├── auth.spec.ts             # Authentication flow
├── tasks.spec.ts            # Task creation
├── quotes.spec.ts           # Quote workflow
└── global-setup.ts          # Test setup
Run tests:
npm run test:e2e      # Headless
npm run test:e2e:ui   # Interactive

Other Directories

Drizzle (/drizzle)

Database migrations (auto-generated):
drizzle/
├── 0001_initial.sql
├── 0002_add_users.sql
├── meta/                    # Migration metadata
└── ...
Don’t edit manually - generate with npm run db:generate

Trigger (/trigger)

Background jobs (Trigger.dev):
trigger/
├── calculate-day-rates.ts   # Daily metric calculation
├── schedule-reminders.ts    # Notification jobs
└── ...

Emails (/emails)

React Email templates:
emails/
├── quote-approved.tsx
├── task-assigned.tsx
└── components/
    └── layout.tsx           # Email layout

Scripts (/scripts)

Utility scripts:
scripts/
├── seed-dev-data.ts         # Seed development data
├── index-user-guide.ts      # Index docs for Algolia
└── ...

File Naming Conventions

TypeConventionExample
ComponentsPascalCaseTaskCard.tsx
Utilitieskebab-casetshirt-sizes.ts
HookscamelCase with use prefixuse-task-detail.ts
API routeskebab-caseroute.ts in day-rates/
TypesPascalCaseTaskStatus
ConstantsSCREAMING_SNAKE_CASEDEFAULT_CAPACITY

Code Organization Principles

1. Colocation

Keep related code close together:
  • Task components in components/tasks/
  • Task services in lib/services/tasks.ts
  • Task schemas in lib/schemas/task.ts
  • Task API in app/api/tasks/

2. Feature-Based Structure

Organize by feature, not by type: ✅ Good:
components/
├── tasks/
│   ├── task-card.tsx
│   ├── task-filters.tsx
│   └── task-wizard.tsx
❌ Bad:
components/
├── cards/
│   └── task-card.tsx
├── filters/
│   └── task-filters.tsx
└── wizards/
    └── task-wizard.tsx

3. Shared vs Feature Code

  • Shared codelib/, components/ui/, hooks/
  • Feature code → Feature-specific directories

4. Thin Routes, Fat Services

API routes should be thin wrappers:
// ✅ Good: Thin route, logic in service
export async function POST(req: Request) {
  const data = await req.json();
  const task = await taskService.create(data);
  return Response.json(task);
}

// ❌ Bad: Business logic in route
export async function POST(req: Request) {
  // 100 lines of validation, calculation, database queries...
}

Adding New Features

When adding a new feature, follow this structure:
1

Create Database Schema

Add tables to lib/db/schema.ts and run npm run db:push
2

Add Service Layer

Create lib/services/your-feature.ts with business logic
3

Create API Routes

Add routes in app/api/your-feature/route.ts
4

Build Components

Add components in components/your-feature/
5

Create Hooks

Add hooks/use-your-feature.ts for data fetching
6

Add Pages

Create pages in app/(dashboard)/your-feature/

Import Paths

CharleOS uses path aliases for clean imports:
// ✅ Good: Path aliases
import { Button } from "@/components/ui/button";
import { createTask } from "@/lib/services/tasks";
import { useTaskDetail } from "@/hooks/use-task-detail";

// ❌ Bad: Relative paths
import { Button } from "../../../components/ui/button";
Configured in tsconfig.json:
  • @/* → Root directory
  • @/components/* → Components
  • @/lib/* → Library code