Skip to main content

Commands

CommandDescription
npm run devStart Next.js dev server
npm run buildBuild both packages for production
npm testRun all unit tests (backend + frontend)
npm run lintLint the frontend (ESLint flat config)
npm run db:generateGenerate Drizzle migration files from schema changes
npm run db:migrateApply pending migrations
npm run db:seedSeed the regulatory knowledge base
npm run db:studioOpen Drizzle Studio (visual DB browser)
npm run test:e2eRun Playwright E2E tests (headless)
npm run test:e2e:uiRun E2E tests with Playwright UI
npm run test:e2e:headedRun E2E tests in headed browser mode

Import patterns

Frontend imports from backend

import { db } from "@verity/backend/db"
import { examinations } from "@verity/backend/schema"
import { REGULATION_CONFIG, getRegulationConfig } from "@verity/backend/config"
import { searchKnowledgeBase } from "@verity/backend/lib"

Frontend internal imports

Always use the @/ path alias for all imports, including sibling components:
// Correct
import { Button } from "@/components/ui/button"
import { formatDate } from "@/lib/utils"

// Wrong — don't use relative imports
import { Button } from "./button"

API patterns

Request/response shape

// Success
{ data: { examinations: [...] } }     // 200
{ data: { examination: {...} } }      // 201

// Error
{ error: { code: "UNAUTHORIZED", message: "..." } }    // 401
{ error: { code: "BAD_REQUEST", message: "..." } }     // 400
{ error: { code: "NOT_FOUND", message: "..." } }       // 404

Route guard pattern

Every protected route follows this sequence:
const session = await getSession()
if (!session) return Response.json(
  { error: { code: "UNAUTHORIZED" } }, { status: 401 }
)

const orgId = getOrgId(session)
if (!orgId) return Response.json(
  { error: { code: "BAD_REQUEST", message: "No active organization" } },
  { status: 400 }
)

// All queries filter by orgId
const rows = await db.select().from(table).where(eq(table.orgId, orgId))

Body validation

POST/PATCH routes validate parsed JSON is a non-null, non-array object before field access:
let body: unknown
try { body = await req.json() } catch { return 400 }
if (typeof body !== "object" || body === null || Array.isArray(body)) return 400

Drizzle patterns

  • Multiple WHERE conditions: and(eq(a), eq(b)) — never chain .where() calls
  • count(*) returns string: use sql<number>`count(*)::int`
  • Vector similarity: always filter isNotNull(embedding) before cosineDistance()
  • Multi-value URL params: searchParams.getAll("key") + inArray() in Drizzle
  • updatedAt columns: use .$onUpdate(() => new Date()) for schema-level auto-refresh

Regulation config

REGULATION_CONFIG in backend/src/config/regulation-types.ts is the single source of truth for:
  • Regulation types and labels
  • Domain categories and labels
  • Obligation types (examination, consent_order, call_report, attestation)
  • Evidence status enum
  • Item status enum
  • Parser prompts and output schemas
Never hardcode category labels, category order, obligation types, or regulation type lists in components or API routes. Always import from @verity/backend/config.

Design tokens

All colors are Tailwind tokens defined in globals.css:
TokenHexClass
Paper#F2F0EBbg-paper
Ink#1C1C1Btext-ink
Forest#2A382Ebg-forest, text-forest
Clay#C9A690border-clay
Stone#D0DCD9bg-stone
Highlight#D4E157bg-highlight
No hardcoded hex colors in components — always use design tokens.

Fonts

FontUsageClass
FrauncesSerif headingsfont-serif
InterBody text, UIDefault
JetBrains MonoIDs, timestamps, status pillsfont-mono

Schema conventions

  • Schema files are grouped by feature, named in kebab-case
  • All tenant-scoped tables must have org_id column (text, not UUID)
  • Knowledge base tables are not org-scoped
  • The column is category, not pillar — renamed across the entire codebase for multi-obligation-type support
  • Human-readable IDs: EX-2026-001, RI-001, MRA-2026-001
  • pgvector columns use vector(1024) dimension (matches Voyage AI output)
  • Embeddings: inputType: 'query' for search, inputType: 'document' for seeding

Rendering strategy

Server components by default. Client components ("use client") only for interactivity:
  • Server — data fetching, auth checks, layout
  • Client — forms, search with debounce, status dropdowns, file uploads, polling
Data flows server → client via props. Dates serialized to ISO strings.