DocsFor Developers

Developer Guide

Everything you need to run, build, and deploy the CDB app platform.


Prerequisites

  • Node.js 22+
  • npm or pnpm
  • Vercel CLI: npm i -g vercel
  • Access to Vercel project (ask Jerry for token)
  • Access to Supabase project (ask Jerry for service key)

Repos

| App | Location | |-----|----------| | Field | ~/.openclaw/workspace/cdb-field/ | | Crew | ~/.openclaw/workspace/cdb-crew/ | | Mission Control | ~/.openclaw/workspace/mission-control/ |


Local Development

Field

cd ~/.openclaw/workspace/cdb-field

# Install dependencies
npm install

# Set up env vars (copy from Vercel or ask Jerry)
cp .env.example .env.local
# Edit .env.local with real values

# Start dev server
npm run dev
# → http://localhost:3000

Required .env.local variables for Field

SUPABASE_URL=https://evfgrjslfrjwyopyzqzx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<from secrets/supabase-service-key.txt>
ALLOWED_USERS=david-7894,jerry-9274,phil-field
GOOGLE_CLIENT_ID=<from google-oauth-client.json>
GOOGLE_CLIENT_SECRET=<from google-oauth-client.json>
GOOGLE_REFRESH_TOKEN=<from google-tokens.json>
TWILIO_ACCOUNT_SID=<twilio console>
TWILIO_AUTH_TOKEN=<twilio console>
TWILIO_FROM_NUMBER=+14155551234
GOOGLE_CALENDAR_ID_1=<calendar id>

Stack

Field

  • Framework: Next.js 14 (App Router)
  • Language: TypeScript
  • Styling: Tailwind CSS v4 + custom CSS variables
  • Database client: Direct Supabase REST API (via supabase-js)
  • Auth: Stateless shared token — stored in localStorage, sent as Authorization: Bearer <token> header
  • SMS: Twilio Node SDK
  • Calendar: Google Calendar API (via googleapis)
  • Markdown: react-markdown
  • Deploy: Vercel

Mission Control

  • Frontend: Vanilla HTML/CSS/JS (single index.html)
  • API: Vercel serverless functions (Node ESM)
  • Deploy: GitHub Actions → Vercel

API Routes Pattern (Field)

All API routes live in app/api/. They follow this pattern:

// app/api/contacts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function GET(req: NextRequest) {
  // 1. Auth check
  const token = req.headers.get('authorization')?.replace('Bearer ', '');
  const allowed = process.env.ALLOWED_USERS?.split(',') ?? [];
  if (!token || !allowed.includes(token)) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 2. Query Supabase
  const { data, error } = await supabase.from('contacts').select('*');
  if (error) return NextResponse.json({ error: error.message }, { status: 500 });

  return NextResponse.json({ contacts: data });
}

Deploying

Field (Vercel CLI)

cd ~/.openclaw/workspace/cdb-field
vercel --token $(cat ~/.openclaw/workspace/secrets/vercel-token.txt) --prod

Mission Control (Git push)

cd ~/.openclaw/workspace/mission-control
git add -A && git commit -m "feat: description"
git push origin main
# GitHub Actions deploys automatically

Database

Supabase project: evfgrjslfrjwyopyzqzx

Running queries

# Via Supabase Management API
curl -X POST \
  "https://api.supabase.com/v1/projects/evfgrjslfrjwyopyzqzx/database/query" \
  -H "Authorization: Bearer $(cat ~/.openclaw/workspace/secrets/supabase-mgmt-token.txt)" \
  -H "Content-Type: application/json" \
  -d '{"query": "SELECT * FROM jobs LIMIT 5;"}'

Key rules

  • Never delete transactions — use status = 'excluded'
  • Every new contact gets pushed to Google Contacts (via /api/contacts)
  • Markup not margin — all estimating UI uses markup percentages

Design System

Field uses CSS custom properties for theming:

--bg:           #181614;   /* page background */
--surface:      #211E1A;   /* cards, sidebar */
--surface-2:    #2E2A25;   /* input backgrounds, hover states */
--mid:          #4A4540;   /* borders on hover */
--muted:        #7A7570;   /* secondary text */
--text:         #EDE8E0;   /* primary text */
--white:        #F7F3EC;   /* headings */
--accent:       #C4622A;   /* CDB orange — CTAs, active states */
--accent-hover: #D4723A;
--border:       #2E2A25;

Tailwind is available but the codebase prefers inline styles and CSS classes (.card, .btn, .badge-*, .page-header, etc.) defined in globals.css.


Adding a New Page

  1. Create app/your-page/page.tsx
  2. Add 'use client'; at the top if it uses hooks or browser APIs
  3. Use page-header + page-body classes for layout consistency
  4. Add to sidebar (components/Sidebar.tsx) under the appropriate section
  5. Add to More page (app/more/page.tsx) if it's a secondary page