Skip to content

Latest commit

 

History

History
257 lines (193 loc) · 7.9 KB

File metadata and controls

257 lines (193 loc) · 7.9 KB

CLAUDE.md — xops Design System & Codebase Rules

Use this file when integrating Figma designs via MCP or making any UI changes.


Stack

  • Framework: Next.js 15 (App Router) in apps/web
  • Styling: Tailwind CSS v3 — utility-first, no CSS Modules, no styled-components
  • Components: Custom components in apps/web/src/components/
  • Icons: lucide-react exclusively — no other icon libraries
  • Animations: Tailwind animate-* + CSS keyframes in globals.css
  • State: React hooks + @tanstack/react-query for server state
  • Web3: wagmi v2 + viem for wallet interactions

Design tokens

All tokens are defined in apps/web/tailwind.config.ts.

Colors

// Brand (indigo-blue)
brand-50   #f0f4ff
brand-400  #7488ff    primary interactive / links
brand-500  #4f5ff7    primary buttons / accent
brand-600  #3a3eec

// Dark surfaces
surface-0  #0a0a0f    page background
surface-1  #111118    sidebar, cards
surface-2  #17171f    elevated cards
surface-3  #1e1e28    inputs, hover states
surface-4  #252531    active states

Typography

font-sans  Inter var (variable font)
font-mono  JetBrains Mono, Fira Code

Spacing / radius

Follow Tailwind defaults. Prefer rounded-lg (8px) for cards, rounded-xl (12px) for larger containers.


Reusable utility classes

Defined in apps/web/src/app/globals.css:

Class Usage
.glass Frosted dark card — bg-white/5 border border-white/10 backdrop-blur-sm
.gradient-text Brand gradient text — for hero headlines
.terminal Dark monospace code block — green text on surface-1
.input Standard form input — dark, focused with brand ring
.deploy-status-dot 8px status indicator dot

Component conventions

File locations

apps/web/src/
  app/              ← Next.js App Router pages
    layout.tsx      ← root layout (Providers)
    page.tsx        ← landing page
    login/          ← public auth pages
    signup/
    app/            ← authenticated app shell
      layout.tsx    ← sidebar + header layout
      page.tsx      ← dashboard
      projects/     ← project management
      credits/
      settings/
    api/            ← API route handlers
  components/       ← shared React components

Naming

  • Files: kebab-case.tsx
  • Components: PascalCase
  • Pages: default export, named XxxPage
  • Server components by default — add 'use client' only when needed

Component anatomy

// Server component (default)
export function MyCard({ title }: { title: string }) {
  return (
    <div className="glass rounded-xl p-6">
      <h2 className="font-semibold">{title}</h2>
    </div>
  );
}

// Client component (stateful)
'use client';
import { useState } from 'react';
export function MyToggle() { ... }

Figma → code mapping

When translating Figma designs:

Figma element Code equivalent
Card / frame <div className="glass rounded-xl p-6">
Primary button bg-brand-500 hover:bg-brand-400 text-white rounded-lg
Secondary button bg-white/5 hover:bg-white/10 border border-white/10
Text input className="input"
Status badge (green) bg-green-500/10 border-green-500/20 text-green-400
Status badge (red) bg-red-500/10 border-red-500/20 text-red-400
Status badge (yellow) bg-yellow-500/10 border-yellow-500/20 text-yellow-400
Code / monospace className="terminal" or font-mono text-xs
Muted label text-white/40 text-xs
Divider border-t border-white/5
Icon Import from lucide-react, w-4 h-4 default size

API patterns

Route handler shape

// Always return ApiResult<T> shape
return NextResponse.json({ data: { ... } });          // success
return NextResponse.json({ error: { message: '...' } }, { status: 4xx }); // error

Auth in API routes

const token = req.cookies.get('xops_session')?.value
  ?? req.headers.get('Authorization')?.replace('Bearer ', '');
const session = await validateSession(token);
if (!session) return NextResponse.json({ error: { message: 'Unauthorized' } }, { status: 401 });

s3worm storage

Package: @decoperations/s3worm (workspace build in this repo; GPR optional for upstream builds)

Singleton access (server-side only):

import { getWorm } from '@/lib/worm'; // apps/web
import { getWorm } from './worm.js'; // apps/builder or packages/build-runner

Key API methods:

worm.save(key, jsonObject); // JSON document
worm.read(key); // read JSON
worm.putBytes(key, data, contentType, opts?); // binary + optional progress
worm.listWithMetadata(prefix); // { key, size, contentType, lastModifiedIso }[]
worm.headObject(key); // metadata
worm.getText(key); // raw UTF-8 text
worm.getPublicUrl(key); // path-style URL when S3_ENDPOINT is set
worm.enableSync({ strategy: 'lww', actor: instanceId }); // hook for oplog-backed builds

Path conventions — always use wormPaths.* from @/lib/worm-paths (web) or ./worm-paths.js (builder / build-runner). Never build key strings ad hoc.

Namespace structure:

users/{userId}/
  profile.json
  credits.json
  projects/{slug}/
    project.json
    settings.json
    deployments/{deployId}/
      manifest.json
      build.json
      logs.txt
      ipfs.json
      artifacts/

Immutable manifests: save() applies IfNoneMatch: '*' when the key ends with manifest.json (first-write policy for deployment records).

Schema codegen: pnpm --filter @xops/web run worm:codegen (or make worm-codegen) regenerates apps/web/src/generated/worm/models.ts from .worm/schema.json.

Monorepo: direct drizzle-orm dependencies

Packages that import drizzle-orm query helpers (eq, and, sql, …) in source must list drizzle-orm in that package’s own package.json (even if they already depend on @xops/db), so pnpm + tsc --noEmit resolve the module. Do not remove those entries without also removing the imports or re-exporting helpers from @xops/db.


Data model quick reference

User          id, email, name, avatarUrl
Wallet        userId, address, chain, verifiedAt
GitHubAccount userId, githubUserId, username
Project       userId, name, slug, repoUrl, framework, buildCommand, outputDir
Deployment    projectId, status, cid, publicUrl, manifestPath, costCredits
CreditLedger  userId, delta, reason, relatedDeploymentId

Deployment status machine: draft → queued → building → publishing → ready | failed


CLI commands

xops login     # device auth flow
xops init      # create xops.json
xops link      # link to remote project
xops deploy    # deploy current directory
xops logs      # tail deployment logs
xops projects  # list projects
xops whoami    # show current user

Environment variables (required)

See .env.example. Minimum to run locally:

  • DATABASE_URL — postgres connection string
  • NEXTAUTH_SECRET — random 32-byte secret
  • GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET — from GitHub OAuth app
  • S3_* — MinIO (local) or AWS S3 credentials

Monorepo commands

make bootstrap    # first-run setup (install + infra + migrations)
make dev          # start everything
make infra-up     # start Postgres + Redis + MinIO
make db-migrate   # run Drizzle migrations
make cli-link     # build + link xops CLI globally
make build        # production build all packages