Files
fyp/AGENTS.md

6.9 KiB

AGENTS.md - Convex Turbo Monorepo

Quick Reference

Build/Lint/Test Commands

# Development
bun dev                      # Run all apps (Next.js + Expo + Backend)
bun dev:next                 # Run Next.js + Convex backend only
bun dev:expo                 # Run Expo + Convex backend only
bun dev:backend              # Run Convex backend only
bun dev:expo:tunnel          # Expo with tunnel for physical device

# Quality
bun lint                     # Lint all packages
bun lint:fix                 # Lint and auto-fix
bun format                   # Check formatting
bun format:fix               # Fix formatting
bun typecheck                # TypeScript type checking

# Build
bun build                    # Build all packages

# Single Package Commands (use Turborepo filters)
bun turbo run dev -F @gib/next           # Single app dev
bun turbo run lint -F @gib/backend       # Lint single package
bun turbo run typecheck -F @gib/ui       # Typecheck single package

# Cleanup
bun clean                    # Clean all node_modules (git clean)
bun clean:ws                 # Clean workspace caches

Project Structure

convex-monorepo/
├── apps/
│   ├── next/           # Next.js 16 web app (@gib/next)
│   └── expo/           # Expo 54 mobile app (@gib/expo)
├── packages/
│   ├── backend/        # Convex backend (@gib/backend)
│   │   └── convex/     # Convex functions, schema, auth
│   └── ui/             # Shared shadcn/ui components (@gib/ui)
├── tools/
│   ├── eslint/         # @gib/eslint-config
│   ├── prettier/       # @gib/prettier-config
│   ├── tailwind/       # @gib/tailwind-config
│   └── typescript/     # @gib/tsconfig
├── docker/             # Self-hosted Convex deployment
└── .env                # Central environment variables

Dependency Management

Catalogs (Single Source of Truth)

All shared dependencies are defined in root package.json catalogs:

"catalog": {
  "prettier": "^3.6.2",
  "typescript": "^5.9.3",
  "eslint": "^9.38.0"
},
"catalogs": {
  "convex": { "convex": "^1.28.0", "@convex-dev/auth": "^0.0.81" },
  "react19": { "react": "^19.1.4", "react-dom": "19.1.4" }
}

Using Catalogs in Packages

"dependencies": {
  "convex": "catalog:convex",
  "react": "catalog:react19",
  "typescript": "catalog:"
},
"devDependencies": {
  "@gib/eslint-config": "workspace:*",
  "@gib/ui": "workspace:*"
}

Updating Dependencies

IMPORTANT: Do NOT use bun update directly - it may replace catalog: with versions.

# Correct workflow:
1. Edit version in root package.json catalog section
2. Run: bun install
3. Verify with: bun lint:ws (runs sherif)

Code Style Guidelines

Imports (via @gib/prettier-config)

// Order: Types → React → Next/Expo → Third-party → @gib/* → Local
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { api } from '@/convex/_generated/api';
import { ConvexError } from 'convex/values';

import { cn } from '@gib/ui';

import type { User } from './types';

TypeScript

  • Strict mode enabled (noUncheckedIndexedAccess: true)
  • Use type imports: import type { X } from 'y'
  • Prefix unused vars with _: const _unused = ...
  • Avoid any - use unknown with type guards

Naming Conventions

  • Components: PascalCase (UserProfile.tsx)
  • Functions/variables: camelCase
  • Constants: SCREAMING_SNAKE_CASE
  • Files: kebab-case.ts (except components)

Error Handling

// Convex functions - use ConvexError
import { ConvexError } from 'convex/values';
throw new ConvexError('User not found.');

// Client-side - handle gracefully
try { ... } catch (e) {
  if (e instanceof ConvexError) { /* handle */ }
}

Formatting

  • Single quotes, trailing commas
  • 80 char line width, 2 space indent
  • Tailwind classes sorted via prettier-plugin-tailwindcss
  • Use cn() for conditional classes: cn('base', condition && 'active')

Environment Variables

Central .env (root)

All env vars in /.env. Apps load via with-env script:

"scripts": {
  "dev": "bun with-env next dev --turbo",
  "with-env": "dotenv -e ../../.env --"
}

Required Variables

# Convex
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
CONVEX_SELF_HOSTED_ADMIN_KEY=<generated>
CONVEX_SITE_URL=https://convex.example.com
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com

# Auth (sync to Convex deployment)
AUTH_AUTHENTIK_ID=
AUTH_AUTHENTIK_SECRET=
AUTH_AUTHENTIK_ISSUER=
USESEND_API_KEY=

Syncing to Convex Deployment

# Upload env vars to self-hosted Convex
npx convex env set AUTH_AUTHENTIK_ID "value"
npx convex env set AUTH_AUTHENTIK_SECRET "value"
npx convex env set AUTH_AUTHENTIK_ISSUER "value"
npx convex env set USESEND_API_KEY "value"
npx convex env set CONVEX_SITE_URL "https://convex.example.com"

Convex Backend Patterns

Schema (packages/backend/convex/schema.ts)

import { defineSchema, defineTable } from 'convex/server';
import { authTables } from '@convex-dev/auth/server';

export default defineSchema({
  ...authTables,
  users: defineTable({ ... }).index('email', ['email']),
});

Queries & Mutations

import { v } from 'convex/values';

import { mutation, query } from './_generated/server';

export const getUser = query({
  args: { userId: v.id('users') },
  handler: async (ctx, args) => {
    return await ctx.db.get(args.userId);
  },
});

Auth

import { getAuthUserId } from '@convex-dev/auth/server';

// In any query/mutation:
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');

Next.js Patterns

Convex Provider Setup

// src/components/providers/ConvexClientProvider.tsx
'use client';

import { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs';
import { ConvexReactClient } from 'convex/react';

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export const ConvexClientProvider = ({ children }) => (
  <ConvexAuthNextjsProvider client={convex}>
    {children}
  </ConvexAuthNextjsProvider>
);

Path Aliases

  • @/*./src/* (Next.js)
  • ~/*./src/* (Expo)

Expo Patterns

NativeWind (Tailwind for RN)

import '../styles.css';

// Use className like web: <View className="flex-1 bg-background" />

Secure Storage for Auth

import * as SecureStore from 'expo-secure-store';

// Tokens stored via SecureStore, not AsyncStorage

Known Issues / Cleanup Needed

  1. Apps reference @acme/_ instead of @gib/_ - Legacy T3 template imports
  2. TRPC imports exist but no TRPC - Replace with Convex client
  3. Expo uses better-auth - Should use @convex-dev/auth
  4. @gib/ui uses pnpm dlx - Should use bunx for shadcn

Adding UI Components

bun ui-add              # Interactive shadcn/ui component addition