Skip to main content
Developer Documentation

Contributing
Guide

How to set up your development environment, follow our code standards, write tests, and get your pull request merged.

Getting Started

Fork the repository, clone your fork, and set up a local development environment before making any changes.

bash
# 1. Fork on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/nxtg.ai.git
cd nxtg.ai

# 2. Install dependencies
pnpm install

# 3. Copy environment template and configure
cp .env.example .env.local
# Fill in NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY

# 4. Start the dev server
pnpm dev

The site will be available at http://localhost:3000. See the Self-Hosting Guide for full environment configuration details.

Development Workflow

All contributions follow the same branching and review process. No commits go directly to main — all changes require a pull request.

1

Create a feature branch from main

bash
git checkout -b feat/your-feature-name

Branch names should be lowercase with hyphens. Prefix with feat/, fix/, docs/, or test/ matching the commit type.

2

Make your changes

Write the implementation and tests together. See the Code Style and Testing sections below for standards.

3

Run the test suite (must pass 100%)

bash
pnpm test

All tests must pass. Test count must not decrease from the previous commit. Zero failures is the only acceptable result.

4

Run the linter

bash
pnpm lint

ESLint must report zero errors. Warnings are acceptable if they are pre-existing, but do not introduce new ones.

5

Commit with a conventional commit message

bash
git commit -m "feat: add docs contributing page"

Include the test count in your commit message: “Tests: X passed, Y skipped”. See the Conventional Commits section below.

6

Push and create a pull request

bash
git push origin feat/your-feature-name

Open a PR against the main branch. Fill in the PR template with a summary and test plan.

Conventional Commits

All commit messages must follow the Conventional Commits specification. The prefix determines how the change appears in the changelog and release notes.

PrefixUse for
feat:A new feature or page
fix:A bug fix
docs:Documentation changes only
test:Adding or updating tests
refactor:Code change that neither fixes a bug nor adds a feature
chore:Dependency updates, build changes, config
cos:ASIF CoS directive chain commit (governance)
Good commit message examples
feat: add self-hosting documentation page (Tests: 32 passed)
fix: correct Clerk middleware redirect URL for /os route
docs: update CLAUDE.md with new admin console sections
test: add missing metadata tests for docs pages

Code Style

Consistency matters more than personal preference. Follow these rules exactly — ESLint and TypeScript will catch most violations automatically.

TypeScript strict mode

All files must pass tsc --noEmit. No implicit any. No type assertions without justification. The type system is the first line of correctness.

Use cn() for conditional classes

Import cn from @/lib/utils for all conditional Tailwind class merging. Never concatenate class strings manually — it breaks tailwind-merge conflict resolution.

Prefer named exports

Components, utilities, and types should use named exports. Default exports are reserved for page.tsx files (Next.js App Router convention).

use client only when needed

Server components are the default. Add 'use client' only when you need browser APIs, event handlers, or React hooks (useState, useEffect, etc.). Keep client components small and push state to the leaves.

Follow shadcn/ui patterns

New UI components should be built on Radix UI primitives and styled with Tailwind, matching the patterns in src/components/ui/. Use CSS variables for colors — never hardcoded hex values.

data-testid on all interactive elements

Every interactive element (buttons, links, inputs, modals) needs a data-testid attribute. Convention: {page}-{section}-{element}. Example: docs-contributing-cta-link.

data-testid convention

Pattern: {page}-{section}-{element}
// Page: docs, Section: contributing, Element: cta-link
data-testid="docs-contributing-cta-link"

// Page: landing, Section: hero, Element: cta-primary
data-testid="landing-hero-cta-primary"

// Page: os, Section: window, Element: terminal-header
data-testid="os-window-terminal-header"

Testing Requirements

This project uses vitest for static analysis tests. Tests live in tests/app/ and perform file-level assertions against page source — verifying metadata, data-testid attributes, imports, and key content.

Every new page needs a test file

When you create a new page at src/app/foo/page.tsx, create a corresponding test at tests/app/foo.test.ts. No exceptions. A page without tests will not be merged.

tests/app/foo.test.ts — minimum structure
import { describe, it, expect } from 'vitest'
import { readFileSync, existsSync } from 'fs'
import { join } from 'path'

describe('Foo page', () => {
  const pagePath = join(process.cwd(), 'src/app/foo/page.tsx')

  it('should exist', () => {
    expect(existsSync(pagePath)).toBe(true)
  })

  const content = readFileSync(pagePath, 'utf-8')

  describe('Metadata and SEO', () => {
    it('should have export const metadata', () => {
      expect(content).toContain('export const metadata: Metadata')
    })

    it('should have title', () => {
      expect(content).toContain('title:')
    })

    it('should have openGraph', () => {
      expect(content).toContain('openGraph')
    })

    it('should have twitter card', () => {
      expect(content).toContain('summary_large_image')
    })

    it('should have canonical URL', () => {
      expect(content).toContain('canonical:')
    })

    it('should have JSON-LD', () => {
      expect(content).toContain('application/ld+json')
    })
  })

  describe('Navigation', () => {
    it('should import Navbar', () => {
      expect(content).toContain("from '@/components/ui/navbar'")
    })

    it('should import Breadcrumbs', () => {
      expect(content).toContain("from '@/components/ui/breadcrumbs'")
    })
  })

  describe('Page structure', () => {
    it('should have page-level data-testid', () => {
      expect(content).toContain('data-testid="foo-page"')
    })

    it('should not have client directive', () => {
      expect(content).not.toContain('use-client-directive')
    })
  })
})

CRUCIBLE Gate 4

Test count must never decrease between commits. If you remove a feature, remove the feature code and its tests together — but the overall test count after your PR must be equal to or greater than before your changes. This is enforced by CRUCIBLE Gate 4 in the ASIF governance protocol.

Minimum test coverage per page

Metadata: title, description, OG, Twitter, canonical
JSON-LD structured data present
Navbar and Breadcrumbs imported
Page-level data-testid attribute
Key content/copy assertions
No use client directive (server components)

Pull Request Process

PR title format

Format: {type}: {description}
feat: add architecture documentation page
fix: correct Clerk redirect for /os route after token expiry
docs: update self-hosting guide with PM2 instructions
test: add contributing page static analysis tests

Keep the title under 72 characters. Use the imperative mood. The prefix must match the conventional commit type for the primary change.

PR description template

Pull request body
## Summary
- What this PR does in 1-3 bullet points
- Why this change is needed

## Test plan
- [ ] pnpm test — all passing (X tests)
- [ ] pnpm lint — zero errors
- [ ] tsc --noEmit — zero type errors
- [ ] New tests added for new files
- [ ] data-testid on all interactive elements

## Files changed
- src/app/... — purpose
- tests/app/... — purpose

Review requirements

At least one approving review from a maintainer before merge
All CI checks must pass (lint, type check, test suite)
No unresolved review comments
Squash and merge is preferred for single-commit PRs. Merge commit for multi-commit feature branches

Ready to contribute?

Fork the repository on GitHub and submit your first pull request.

View on GitHub