Skip to content

Latest commit

Β 

History

History
404 lines (294 loc) Β· 12.6 KB

File metadata and controls

404 lines (294 loc) Β· 12.6 KB

CLAUDE.md

This file provides guidance to Claude Code when working with this codebase.

Documentation Policy

DO NOT create random .md documentation files when implementing features.

  • Provide explanations in chat messages
  • Add comments in code where necessary
  • Update existing documentation (CLAUDE.md, README.md) only if required
  • Only create documentation files if explicitly requested

Project Overview

A T3 Stack application for team management with visual organization canvases, role & metric tracking, and multi-tenant architecture.

Core Stack:

  • Next.js 15 (App Router)
  • tRPC 11 with TanStack Query
  • Prisma 6 with PostgreSQL (Accelerate caching)
  • WorkOS AuthKit authentication
  • React Flow for canvas visualizations
  • Zustand for local state
  • Tailwind CSS with shadcn/ui

Development Commands

# Development
pnpm dev              # Start dev server with Turbo
pnpm build            # Build for production
pnpm preview          # Build and start production server

# Code Quality
pnpm check            # Run linting and type checking
pnpm lint:fix         # Auto-fix ESLint issues
pnpm format:write     # Format code with Prettier

# Database
pnpm db:generate      # Generate Prisma client
pnpm db:push          # Push schema changes
pnpm db:studio        # Open Prisma Studio

# Testing
pnpm exec playwright test                          # Run all tests
pnpm exec playwright test --project=chromium       # Chromium only
pnpm exec playwright test tests/auth-authenticated.spec.ts  # Specific file

Directory Structure

src/
β”œβ”€β”€ app/                           # Next.js pages
β”‚   β”œβ”€β”€ _components/               # Root-level page components (landing)
β”‚   β”œβ”€β”€ api/                       # API routes (cron, callbacks)
β”‚   β”œβ”€β”€ dashboard/[teamId]/        # Metrics dashboard
β”‚   β”œβ”€β”€ docs/                      # MDX documentation
β”‚   β”œβ”€β”€ integration/               # Integration management
β”‚   β”œβ”€β”€ metric/_components/        # Metric dialogs (per provider)
β”‚   β”œβ”€β”€ org/                       # Organization settings
β”‚   β”œβ”€β”€ public/                    # Public-facing views
β”‚   └── teams/[teamId]/            # Team canvas (React Flow)
β”‚
β”œβ”€β”€ components/                    # Shared UI
β”‚   β”œβ”€β”€ ui/                        # shadcn/ui components (54 files)
β”‚   β”œβ”€β”€ charts/                    # Recharts wrappers
β”‚   β”œβ”€β”€ navbar/                    # Navigation
β”‚   └── react-flow/                # BaseNode, BaseHandle, ZoomSlider
β”‚
β”œβ”€β”€ lib/                           # Utilities
β”‚   β”œβ”€β”€ canvas/                    # Reusable React Flow library
β”‚   β”œβ”€β”€ integrations/              # Provider configurations
β”‚   β”œβ”€β”€ metrics/                   # Transformer types
β”‚   └── helpers/                   # Helper functions
β”‚
β”œβ”€β”€ server/                        # Server-only code
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ routers/               # tRPC routers (12 total)
β”‚   β”‚   β”œβ”€β”€ services/              # Business logic
β”‚   β”‚   └── utils/                 # Authorization, caching
β”‚   └── db.ts                      # Prisma singleton
β”‚
β”œβ”€β”€ trpc/                          # tRPC client setup
β”œβ”€β”€ providers/                     # React context providers
β”œβ”€β”€ hooks/                         # Shared hooks
└── middleware.ts                  # Auth middleware

Architecture Patterns

Authentication Flow

WorkOS middleware runs on all routes. Public routes: /, /docs, /public/*.

// tRPC: protectedProcedure validates ctx.user
// No manual auth checks needed in components
// NavBar uses try-catch for graceful auth handling

tRPC Dual API Pattern

// Server Components: Direct calls (10x faster)
import { api } from "@/trpc/server";
const data = await api.team.getById({ id });

// Client Components: React hooks with TanStack Query
import { api } from "@/trpc/react";
const { data } = api.team.getById.useQuery({ id });

Adding New tRPC Routes

  1. Create router in src/server/api/routers/[name].ts
  2. Use protectedProcedure or workspaceProcedure
  3. Add to appRouter in src/server/api/root.ts
  4. Add authorization checks using utils from authorization.ts

Authorization Helpers

import {
  getMetricAndVerifyAccess,
  getRoleAndVerifyAccess,
  getTeamAndVerifyAccess,
} from "@/server/api/utils/authorization";

// Always verify resource belongs to user's organization
const team = await getTeamAndVerifyAccess(db, teamId, userId, workspace);

Team Canvas System

The team canvas (/teams/[teamId]) is a React Flow-based visualization with 30 files.

Data Flow

page.tsx (Server)
  β†’ Prefetch: role.getByTeamId, organization.getMembers
  β†’ enrichNodesWithRoleData(storedNodes)
  β†’ <HydrateClient>
    β†’ <TeamStoreProvider> (Zustand)
      β†’ <ChartDragProvider>
        β†’ <TeamCanvas> (React Flow)

Node Types

Type Data Stored Display Source
role-node { roleId } TanStack Query cache
text-node { text, fontSize } Direct node.data
chart-node { dashboardMetricId } Database via props
freehand { points } Session only (not saved)

Key Pattern: Cache-First Nodes

Role nodes store ONLY roleId. Display data fetched from TanStack Query cache:

// use-role-data.tsx
export function useRoleData(roleId: string) {
  const { data: roles } = api.role.getByTeamId.useQuery({ teamId });
  return useMemo(() => roles?.find((r) => r.id === roleId), [roles, roleId]);
}

Store Pattern

// Zustand + Context pattern in team-store.tsx
const TeamStoreContext = createContext<StoreApi<TeamStore> | null>(null);

// Access in components
const nodes = useTeamStore((state) => state.nodes);
const storeApi = useTeamStoreApi(); // For callbacks (avoids stale closures)

Auto-Save System

Canvas changes β†’ markDirty() β†’ Debounce 2s β†’ serializeNodes/Edges β†’ tRPC mutation
                                              ↓
                                    beforeunload: sendBeacon fallback

Cache Pipeline: Role Mutations

Two cache layers: TanStack Query (client) and Prisma Accelerate (server).

User updates role
  β†’ onMutate: Optimistic update (instant UI feedback)
  β†’ Server mutation runs
  β†’ onSuccess:
      1. setData(updatedRole)  ← Critical: use server response
      2. invalidate()          ← Background refresh
  β†’ onError: Rollback to previousData

Why setData before invalidate? Prisma Accelerate cache may not propagate immediately. If we only call invalidate(), the refetch might return stale data. Setting cache with server response ensures correct data.

Key files:

  • src/hooks/use-optimistic-role-update.ts - Shared hook for all role mutations
  • src/app/teams/[teamId]/hooks/use-update-role.tsx - Canvas-specific wrapper (adds markDirty)

Canvas Library (src/lib/canvas/)

Reusable patterns for React Flow canvases:

src/lib/canvas/
β”œβ”€β”€ store/create-canvas-store.tsx   # Generic store factory
β”œβ”€β”€ hooks/use-auto-save.ts          # Debounced save hook
β”œβ”€β”€ components/save-status.tsx      # Save indicator UI
β”œβ”€β”€ edges/edge-action-buttons.tsx   # Edge interaction buttons
β”œβ”€β”€ edges/floating-edge-utils.ts    # Edge path calculations
└── freehand/                       # Drawing mode components

Metrics Pipeline

Three-Stage Transformation

Stage 1: API β†’ DataPoints
  fetchData() β†’ DataIngestionTransformer (AI-generated) β†’ MetricDataPoint[]

Stage 2: DataPoints β†’ ChartConfig
  MetricDataPoint[] β†’ ChartTransformer (AI-generated) β†’ ChartConfig

Stage 3: ChartConfig β†’ UI
  ChartConfig β†’ DashboardMetricChart (Recharts)

Key Models

  • Metric: Core metric with integrationId, templateId, pollFrequency
  • MetricDataPoint: Time-series data (unique on metricId + timestamp)
  • DashboardChart: Chart configuration linked to Metric
  • DataIngestionTransformer: AI code for API β†’ DataPoints
  • ChartTransformer: AI code for DataPoints β†’ ChartConfig

Polling System

Cron (/api/cron/poll-metrics) runs every 15 minutes for metrics with nextPollAt <= now().

Poll frequencies: frequent (15m), hourly, daily, weekly, manual

Adding New Integrations

  1. Create provider config in src/lib/integrations/
  2. Add metric dialog in src/app/metric/_components/[provider]/
  3. Create [Provider]MetricDialog.tsx + [Provider]MetricContent.tsx
  4. Register in src/app/metric/_components/index.ts

Dashboard KPI Page

The dashboard (/dashboard/[teamId]) displays metric charts with role assignments.

Cache Pipeline: Dashboard Charts

Uses dashboard.getDashboardCharts query which includes nested role data.

Role-metric assignment changes
  β†’ onMutate: Update both role + dashboard caches optimistically
  β†’ Server mutation
  β†’ onSuccess:
      1. setData for role cache (server response)
      2. invalidate both role + dashboard caches

Role assignments appear in two places:

  • Role cache: role.getByTeamId - role has metricId field
  • Dashboard cache: dashboard.getDashboardCharts - chart.metric.roles array

When linking/unlinking roles to metrics, both caches must be updated for consistent UI.

Key files:

  • src/hooks/use-optimistic-role-update.ts - Updates both caches
  • src/components/metric/role-assignment.tsx - Role assignment UI in metric drawer

Environment Variables

Required (see .env.example):

DATABASE_URL           # PostgreSQL connection
WORKOS_API_KEY         # WorkOS API key
WORKOS_CLIENT_ID       # WorkOS client ID
WORKOS_COOKIE_PASSWORD # 32-char session secret
NEXT_PUBLIC_WORKOS_REDIRECT_URI  # OAuth callback
TEST_USER_EMAIL        # Playwright test user
TEST_USER_PASSWORD     # Playwright test password

Validated via @t3-oss/env-nextjs in src/env.js.

Code Quality

Pre-commit hooks (Husky + lint-staged):

  • ESLint auto-fix
  • Prettier formatting
  • Runs on staged files only

Import sorting: @trivago/prettier-plugin-sort-imports with inline type imports.

Component Patterns

shadcn/ui Components

Located in src/components/ui/. Use CLI for new components:

npx shadcn@latest add [component-name]

React Flow Primitives

Shared in src/components/react-flow/:

  • BaseNode - Styled node container
  • BaseHandle - Styled handles
  • ZoomSlider - Zoom controls

Metric Dialogs

Each integration in src/app/metric/_components/:

github/
  β”œβ”€β”€ GitHubMetricDialog.tsx    # Dialog wrapper
  └── GitHubMetricContent.tsx   # Form content
linear/
  β”œβ”€β”€ LinearMetricDialog.tsx
  └── LinearMetricContent.tsx

Uses shared MetricDialogBase from base/.

Testing

Playwright E2E tests in tests/.

  • Global setup handles authentication
  • Use authenticatedPage fixture for auth-required tests
  • Tests run against http://localhost:3000

Common Tasks

Adding Canvas Node Types

  1. Create node component in teams/[teamId]/_components/
  2. Add type to TeamNode union in types/canvas.ts
  3. Register in nodeTypes in team-canvas.tsx
  4. Update serialization in canvas-serialization.ts

Adding tRPC Procedures

  1. Add procedure to appropriate router
  2. Use workspaceProcedure for org-scoped operations
  3. Call authorization helpers for resource verification
  4. Invalidate dashboard cache after mutations:
    import { invalidateDashboardCache } from "@/server/api/utils/cache-strategy";
    
    await invalidateDashboardCache(ctx.db, organizationId, teamId);

Consolidated Components

MembersList: Shared component at src/components/member/member-list.tsx

  • Used by canvas sidebar (canvas-side-panels.tsx) and org page (MembersListClient.tsx)
  • Includes getMemberDisplayInfo() utility for initials/name logic

Role Enrichment: Shared utilities in src/server/api/utils/organization-members.ts

  • enrichRolesWithUserNames() - for flat role arrays
  • enrichChartRolesWithUserNames() - for nested chart roles
  • Both use shared internal helpers to avoid duplication

User Display Names:

  • Client-side (sync): getUserDisplayName(userId, members) from @/lib/helpers/get-user-name
  • Server-side (async WorkOS): fetchUserDisplayName(userId) from @/server/api/utils/get-user-display-name

Known Issues

Performance Issues

  • MetricApiLog writes on every fetch (debugging overhead in production)
  • Double data point fetching on metric refresh
  • Goal calculation utility is 271 lines (could be 50)