Skip to content

Latest commit

 

History

History
218 lines (158 loc) · 10.6 KB

File metadata and controls

218 lines (158 loc) · 10.6 KB

AGENTS.md

This file provides guidance to AI coding agents (Codex, Cursor, Claude, etc.) when working with code in this repository.

Development Commands

Essential Commands

pnpm install          # Install dependencies (uses pnpm, not yarn!)
pnpm dev              # Start development server (default port 5173)
pnpm build            # Build for production (tsc + vite build)
pnpm lint             # Run ESLint
pnpm format           # Format code with Prettier
pnpm test:unit        # Run unit tests (Vitest)
pnpm preview          # Preview the production build locally
pnpm generate:types   # Generate OpenAPI types: pnpm generate:types -- <base-url>

Authentication & Proxy

By default, API requests are relative (/api/v1/...). In development, the Vite dev server proxies /api requests to VITE_BACKEND_URL (default http://localhost:8237) so frontend and backend appear on the same origin. For cross-origin deployments, set VITE_API_BASE_URL to an absolute backend origin (for example https://backend.test.com) and ensure backend CORS/cookie settings allow credentialed requests from the frontend origin.

Architecture Overview

Technology Stack

  • Framework: Vite 7 + React 19 (SPA)
  • Routing: @tanstack/react-router (file-based with auto code-splitting)
  • Server state: @tanstack/react-query
  • Forms: React Hook Form + Zod validation
  • Styling: Tailwind CSS v4 (via @tailwindcss/vite plugin)
  • UI primitives: Base UI (@base-ui/react) + class-variance-authority, with shadcn-style config conventions (components.json)
  • Icons: @untitledui/icons
  • Notifications: Sonner + next-themes
  • Type safety: TypeScript (strict mode) with generated types from ZenML OpenAPI spec
  • Compiler: React Compiler (via Babel plugin)
  • Testing: Vitest
  • Pre-commit: Lefthook (ESLint auto-fix + Prettier on staged files)

Project Structure

  • The intended module-based structure lives under src/modules/<module>.
  • Each module should be split by responsibility using these layers:
    • domain/ — module-owned types and actual API/request functions
    • business-logic/ — TanStack Query definitions and other module-specific orchestration/business logic
    • feature/ — stateful containers, provider composition, and feature entrypoints
    • util/ — small module-scoped utilities
    • ui/ — stateless presentational components
  • Keep modules split by layer from the start rather than growing a flat module and reorganizing later.
  • src/routes/* — file-based TanStack Router route definitions; these contain beforeLoad logic for data preloading and redirects, plus page metadata
  • src/shared/api/domain/* — transport layer: apiClient, endpoint path constants, FetchError class
  • src/shared/api/utils/* — URL builders, querystring helpers, error response handling
  • src/shared/api/types.ts — generated OpenAPI types (do not hand-edit)
  • src/shared/api/* must not import router concerns (notFound, route context, etc.); keep router-aware helpers in src/shared/router/ or module layers
  • src/shared/router/utils/* — shared router helpers (e.g. ensureQueryDataOr404)
  • src/shared/ui/* — reusable UI primitives built on Base UI; these are the shadcn-managed surface referenced by components.json
  • src/shared/utils/* — shared utilities (styles.ts for cn(), build-page-titles.ts)
  • Root Module bootstrap code (queryClient, root providers) belongs in src/modules/root/
  • Root Module global resources (server info, session, config) belong in src/modules/root/domain/*
  • Assets (icons/images) live in src/assets and can be imported as React components via SVGR

Generated Files

Two files are auto-generated and excluded from ESLint. Do not hand-edit them:

  • src/routeTree.gen.ts — generated by the TanStack Router Vite plugin from src/routes/
  • src/shared/api/types.ts — generated by pnpm generate:types from the ZenML OpenAPI spec

General Best Practices

  • Prefer composition over inheritance
  • Keep components focused; lift state only as needed
  • Use component variants (via cva) for styling variations rather than inline conditionals
  • Prefer writing Tailwind classes in the ui layer, but feature/layout shells may use them when intentional
  • Avoid duplicating code or inventing hyper-generic abstractions: inspect existing flows before writing new components or helpers
  • Prefer focused components over catch-all versions; duplicating two purposeful components is often clearer than a single complex abstraction
  • Reference existing implementations for similar features

Data Fetching Pattern

All API interactions follow a consistent pattern. Request definitions belong to the owning module, not generic shared folders.

Request functions — define actual read/write API functions in src/modules/<module>/domain/*.

  • Keep these functions focused on transport and response parsing.
  • Do not export custom React Query fetcher helpers for reads when queryOptions(...) can express the query API directly.

Queries — define TanStack Query keys and query collections in src/modules/<module>/business-logic/*.

  • Export grouped key factories such as xQueryKeys.
  • Export grouped query factories such as xQueries.
  • Build read APIs with queryOptions(...) or infiniteQueryOptions(...) so they are reusable from both route loaders and components.

Mutations — define and export mutation hooks from src/modules/<module>/business-logic/*.

  • Keep the underlying write request function in domain/.
  • Wrap useMutation(...) in a module hook (e.g. useVerifyDevice, useLoginUser).
  • Return the mutation result plus domain-named aliases for mutate/mutateAsync (e.g. verifyDevice, verifyDeviceAsync) for ergonomic usage in features.
  • Do not use mutationOptions(...) for module mutations.

Components should consume the exported module mutation hooks directly.

Cross-module orchestration — when a mutation needs to chain multiple domain actions (e.g. activate server then login), define that orchestration in the owning module's feature/ layer.

Example query:

// src/modules/device/business-logic/device-queries.ts
import { queryOptions } from "@tanstack/react-query";

import { fetchDevice } from "@/modules/device/domain/fetch-device";
import type { DeviceQueryParams } from "@/modules/device/domain/device-query-params";

export const deviceQueryKeys = {
	all: ["device"] as const,
	detail: (deviceId: string) => [...deviceQueryKeys.all, deviceId] as const,
};

export const deviceQueries = {
	detail: (deviceId: string, queryParams: DeviceQueryParams = {}) =>
		queryOptions({
			queryKey: [...deviceQueryKeys.detail(deviceId), queryParams],
			queryFn: () => fetchDevice(deviceId, queryParams),
		}),
};

Current data loading flow:

  • Route beforeLoad handlers call context.queryClient.ensureQueryData(...) to preload data
  • Components use module mutation hooks for write operations
  • Global 401 handling: QueryCache.onError in query-client.ts redirects to /login?next=... on FetchError with status 401
  • Mutation errors are handled locally in the form/container that triggered them

Path Aliasing

The codebase uses @/* as an alias for src/*:

import { apiClient } from "@/shared/api/domain/api-client";
import { deviceQueries } from "@/modules/device/business-logic/device-queries";

Configured in both tsconfig.json and vite.config.ts (via vite-tsconfig-paths).

Form Handling

Forms use React Hook Form + Zod:

  • Define a Zod schema in domain/<name>-schema.ts
  • Use zodResolver(schema) with useForm(...) in the form container
  • Use Controller for controlled field components
  • Wire submission to the module's exported mutation hook
  • Show errors via toast notifications (Sonner)

See ServerActivationFormContainer.tsx and LoginFormContainer.tsx for current examples in the intended naming scheme.

Components & Styling

  • Reusable UI primitives live in src/shared/ui/* and are built on Base UI with cva for variants
  • src/shared/ui/* and src/shared/utils/styles.ts are shadcn-managed surfaces referenced by components.json; avoid refactoring them unless explicitly requested
  • Icons and illustrations live in src/assets and can be imported as React components via SVGR
  • Keep Tailwind utility classes; Prettier (with the Tailwind plugin) auto-sorts them
  • Prefer focused components over overly generic abstractions

See DESIGN.md for design-related guidelines.

Coding Conventions

  • Define React components with function declarations instead of arrow functions
  • Stick to strict typing: no any, prefer type aliases, and colocate types near usage
  • No type casting
  • Use PascalCase for React component and context files (e.g. Dashboard.tsx, DashboardContainer.tsx, AuthContext.tsx)
  • Use kebab-case for hooks, utilities, API calls, and domain-layer files (e.g. use-pipeline.tsx, api-client.ts, fetch-device.ts)
  • Exception: route files should follow TanStack Router naming requirements when those differ (e.g. pathless/layout route conventions)
  • define optional props/params like this: selectedId?: string instead of selectedId: string | null or selectedId: string | undefined

Networking

  • apiClient sends credentials: "include" and targets /api/v1/... by default
  • Optional override: set VITE_API_BASE_URL to send requests to <origin>/api/v1/...
  • Default headers: Content-Type: application/json, Source-Context: dashboard-v2
  • Login is a special case that overrides content type to application/x-www-form-urlencoded
  • Vite proxies /api to VITE_BACKEND_URL in development
  • Non-OK responses throw FetchError (see throw-fetch-error-from-response.ts)

Assets

  • Place all icons/images in src/assets and import them as React components via SVGR; reuse existing assets before adding new ones

Git Conventions

Docs Maintenance

Before committing or pushing a PR, check whether README.md and AGENTS.md need updating to reflect your changes. Common triggers:

  • New or renamed folders, modules, or routes
  • Added/removed dependencies or scripts
  • Changes to the data fetching, auth, or networking patterns
  • New generated files or build steps

Keep both files accurate — stale docs erode trust faster than missing docs.

PR Titles

  • Use plain, descriptive titles without conventional commit prefixes (no feat:, fix:, ci:, etc.)
  • Good: "Add workflow to require release label on PRs"
  • Bad: "ci: add workflow to require release label on PRs"

CI

GitHub Actions (.github/workflows/build-validation.yml) runs on push to main and on all PRs:

  1. pnpm install --frozen-lockfile
  2. pnpm lint
  3. pnpm build
  4. pnpm test:unit