Skip to content

Code Style Guide

Coding standards and conventions for the Constellation project.

Table of Contents

  1. TypeScript/JavaScript
  2. React Components
  3. File Organization
  4. Naming Conventions
  5. Import Organization
  6. Code Formatting

TypeScript/JavaScript

Use Strict Type Mode

// โœ… GOOD
function processUser(user: User): Promise<void> {
  // ...
}

// โŒ BAD
function processUser(user: any): any {
  // ...
}

Prefer Const/Let Over Var

// โœ… GOOD
const CONSTANT_VALUE = 'value';
let mutableValue = 0;

// โŒ BAD
var oldStyle = 'avoid this';

Use Template Literals

// โœ… GOOD
const message = `Hello, ${name}! You have ${count} items.`;

// โŒ BAD
const message = 'Hello, ' + name + '! You have ' + count + ' items.';

Avoid Double Negatives

// โœ… GOOD
const isAvailable = true;
if (isAvailable) { }

// โŒ BAD
const isNotUnavailable = true;
if (!isNotUnavailable) { }

Use Nullish Coalescing

// โœ… GOOD
const value = config.value ?? 'default';

// โŒ BAD
const value = config.value || 'default'; // Fails for falsy values

Destructuring

// โœ… GOOD
const { name, email } = user;
const { title = 'Untitled' } = props;

// โŒ BAD
const name = user.name;
const email = user.email;

React Components

Naming Convention

// โœ… GOOD - PascalCase for components
export function UserProfile() { }
export const WorkspaceCard = () => { };

// โŒ BAD - camelCase for components
export function userProfile() { }
export const workspaceCard = () => { };

Functional Components Only

// โœ… GOOD
export function MyComponent() {
  return <div>Content</div>;
}

// โŒ BAD - Class components (legacy)
class MyComponent extends React.Component { }

Component Structure

'use client';

import React from 'react';
import { useHook } from '@/src/hooks/use-hook';
import { Button } from '@/src/components/ui/Button';

interface ComponentProps {
  title: string;
  onChange?: (value: string) => void;
}

export function MyComponent({ title, onChange }: ComponentProps) {
  const [state, setState] = React.useState('');

  return (
    <div>
      <h1>{title}</h1>
      <Button onClick={() => setState('new')}>Click me</Button>
    </div>
  );
}

Order: 1. Imports 2. Type/Interface definitions 3. Component declaration 4. Props interface 5. Hook usage 6. State and effects 7. Event handlers 8. Render JSX

Props Interface

// โœ… GOOD
interface CardProps {
  title: string;
  description?: string;
  onClose: () => void;
  children?: React.ReactNode;
}

export function Card({ title, description, onClose, children }: CardProps) {
  // ...
}

// โŒ BAD - Inline props without type
export function Card(props: any) {
  // ...
}

Use 'use client' For Interactive Components

// โœ… GOOD
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Avoid Direct DOM Manipulation

// โœ… GOOD
export function InputField({ value, onChange }: Props) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

// โŒ BAD
export function InputField() {
  useEffect(() => {
    document.getElementById('input').value = 'test'; // Direct manipulation
  }, []);

  return <input id="input" />;
}

Key in Lists

// โœ… GOOD
{items.map((item) => (
  <Item key={item.id} {...item} />
))}

// โŒ BAD - Using index as key
{items.map((item, index) => (
  <Item key={index} {...item} />
))}

Conditional Rendering

// โœ… GOOD - Ternary for simple conditions
{isLoading ? <Spinner /> : <Content />}

// โœ… GOOD - Logical AND for show/hide
{isVisible && <Content />}

// โœ… GOOD - Early return for complex logic
if (!isReady) return null;

// โŒ BAD - Inline if-else
{if (isLoading) return <Spinner />} {/* Syntax error */}

File Organization

File Structure

src/
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ ui/                          # Shadcn/ui components
โ”‚   โ”‚   โ”œโ”€โ”€ Button.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ Card.tsx
โ”‚   โ”‚   โ””โ”€โ”€ index.ts                 # Export barrel
โ”‚   โ”œโ”€โ”€ common/                      # Shared components
โ”‚   โ”‚   โ”œโ”€โ”€ Header.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ Footer.tsx
โ”‚   โ”‚   โ””โ”€โ”€ Header.test.tsx
โ”‚   โ””โ”€โ”€ workspace/                   # Feature-specific
โ”‚       โ”œโ”€โ”€ WorkspaceCard.tsx
โ”‚       โ”œโ”€โ”€ WorkspaceCard.test.tsx
โ”‚       โ””โ”€โ”€ WorkspaceList.tsx
โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ use-mobile.tsx
โ”‚   โ”œโ”€โ”€ use-mobile.test.tsx
โ”‚   โ””โ”€โ”€ index.ts                     # Export barrel
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ api.ts                       # API client
โ”‚   โ”œโ”€โ”€ utils.ts                     # Utilities
โ”‚   โ”œโ”€โ”€ auth.ts                      # Auth logic
โ”‚   โ””โ”€โ”€ schemas/                     # Zod schemas
โ”‚       โ”œโ”€โ”€ user.ts
โ”‚       โ””โ”€โ”€ workspace.ts
โ”œโ”€โ”€ services/
โ”‚   โ”œโ”€โ”€ userService.ts
โ”‚   โ”œโ”€โ”€ workspaceService.ts
โ”‚   โ””โ”€โ”€ apiClient.ts
โ””โ”€โ”€ types/
    โ”œโ”€โ”€ user.ts
    โ”œโ”€โ”€ workspace.ts
    โ””โ”€โ”€ index.ts                     # Export barrel

One Component Per File

// โœ… GOOD
MyComponent.tsx          # Component
MyComponent.test.tsx     # Tests
MyComponent.stories.tsx  # Storybook (if using)

// โŒ BAD
MyComponents.tsx         # Multiple components in one file

Barrel Exports

// โœ… GOOD - src/components/ui/index.ts
export { Button } from './Button';
export { Card } from './Card';
export { Dialog } from './Dialog';

// Usage
import { Button, Card } from '@/src/components/ui';

Naming Conventions

Constants

// โœ… GOOD - UPPER_SNAKE_CASE
const MAX_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';
const FEATURE_FLAGS = { BETA_EDITOR: true };

// โŒ BAD
const maxAttempts = 3;
const api_url = 'https://api.example.com';

Functions & Variables

// โœ… GOOD - camelCase
function getUserData() { }
const userName = 'John';
const isActive = true;

// โŒ BAD
function get_user_data() { }
const user_name = 'John';
const IsActive = true;

Boolean Variables

// โœ… GOOD - prefix with is/has/can
const isLoading = true;
const hasError = false;
const canEdit = true;
const shouldRefresh = false;

// โŒ BAD
const loading = true;
const error = false;

Event Handlers

// โœ… GOOD - prefix with on
function handleClick() { }
function handleSubmit() { }
const onClick = () => { };

// โŒ BAD
function click() { }
function submit() { }
// โœ… GOOD - clear intent
async function fetchUserData() { }
async function createWorkspace() { }
function deleteWorkspace() { }

// โŒ BAD
async function getUser() { }
async function addWorkspace() { }

Import Organization

Import Order

// 1. External packages
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';

// 2. Internal modules (absolute imports)
import { Button } from '@/src/components/ui/Button';
import { useWorkspace } from '@/src/hooks/use-workspace';
import { apiClient } from '@/src/lib/api';
import type { User } from '@/src/types/user';

// 3. Relative imports (if necessary)
import { helper } from './helper';

// 4. Side effects
import './styles.css';

Absolute Imports

// โœ… GOOD - Absolute imports
import { Button } from '@/src/components/ui/Button';
import { useWorkspace } from '@/src/hooks/use-workspace';

// โŒ BAD - Relative imports (hard to refactor)
import { Button } from '../../components/ui/Button';
import { useWorkspace } from '../../../hooks/use-workspace';

Type Imports

// โœ… GOOD - Separate type imports
import { Button } from '@/src/components/ui/Button';
import type { ButtonProps } from '@/src/components/ui/Button';

// โŒ BAD - Mixed imports
import { Button, type ButtonProps } from '@/src/components/ui/Button';

Code Formatting

Line Length

Keep lines under 100 characters for readability.

// โœ… GOOD
const formattedText = `Welcome ${userName}, you have ${itemCount} items`;

// โŒ BAD - Too long
const excessivelyLongVariableNameThatMakesLineUnreadablyLongWhenUsed = 'value';

Function Parameters

// โœ… GOOD
export function processUserData(
  userId: string,
  options: ProcessOptions,
  callback: (result: Result) => void
): Promise<void> {
  // ...
}

// โŒ BAD
export function processUserData(userId: string, options: ProcessOptions, callback: (result: Result) => void): Promise<void> {
  // ...
}

Object Formatting

// โœ… GOOD - Single line for simple objects
const config = { timeout: 5000, retries: 3 };

// โœ… GOOD - Multi-line for complex objects
const config = {
  timeout: 5000,
  retries: 3,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
  },
};

Comments

// โœ… GOOD - Explains why, not what
// Cache user data for 5 minutes to reduce API calls
const USER_CACHE_TTL = 300000;

// โœ… GOOD - Explains non-obvious code
// Convert timestamp to milliseconds (API returns seconds)
const timestampMs = timestamp * 1000;

// โŒ BAD - Obvious comments
// Set loading to true
setLoading(true);

// โŒ BAD - Outdated comments
// This fixes bug #123 (bug no longer exists)
const value = config.value ?? 0;

JSDoc for Public APIs

// โœ… GOOD
/**
 * Formats a date for display
 * @param date - The date to format
 * @param locale - Optional locale string (default: 'en-US')
 * @returns Formatted date string
 * @example
 * formatDate(new Date('2024-01-15'))
 * // Returns: 'Jan 15, 2024'
 */
export function formatDate(date: Date, locale: string = 'en-US'): string {
  // ...
}

Tools & Automation

ESLint

# Check code style
bun lint

# Auto-fix issues
bun lint --fix

Prettier

Configured automatically for: - Indentation (2 spaces) - Line length (100 characters) - Semicolons - Single quotes - Trailing commas

TypeScript Strict Mode

Enable in tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

Common Patterns

Custom Hook Pattern

// โœ… GOOD
export function useUserData(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    (async () => {
      try {
        const data = await apiClient.get(`/users/${userId}`);
        setUser(data);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    })();
  }, [userId]);

  return { user, loading, error };
}

Component with API Call

// โœ… GOOD
'use client';

interface UserCardProps {
  userId: string;
}

export function UserCard({ userId }: UserCardProps) {
  const { user, loading, error } = useUserData(userId);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return null;

  return (
    <div className="p-4 border rounded">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Error Handling

// โœ… GOOD
async function fetchData() {
  try {
    const response = await apiClient.get('/data');
    return response;
  } catch (error) {
    logger.error('Failed to fetch data', error);
    throw new Error('Failed to fetch data');
  }
}

Checklist Before Commit

  • โœ… All TypeScript types are correct
  • โœ… No any types used (except where necessary)
  • โœ… Components are properly named (PascalCase)
  • โœ… Imports are organized correctly
  • โœ… No console.log statements (use logger)
  • โœ… Error handling is present
  • โœ… Tests are written
  • โœ… ESLint passes: bun lint
  • โœ… Code is readable and well-commented
  • โœ… No hardcoded values in components


Last Updated: January 2026