6.9 KiB
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
typeimports:import type { X } from 'y' - Prefix unused vars with
_:const _unused = ... - Avoid
any- useunknownwith 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
- Apps reference @acme/_ instead of @gib/_ - Legacy T3 template imports
- TRPC imports exist but no TRPC - Replace with Convex client
- Expo uses better-auth - Should use @convex-dev/auth
- @gib/ui uses pnpm dlx - Should use bunx for shadcn
Adding UI Components
bun ui-add # Interactive shadcn/ui component addition