Skip to content

Latest commit

 

History

History
663 lines (497 loc) · 16.5 KB

File metadata and controls

663 lines (497 loc) · 16.5 KB

Contributing to Object UI

Thank you for your interest in contributing to Object UI! This document provides guidelines and instructions for contributing to the project.

Table of Contents

Getting Started

Prerequisites

  • Node.js 18.0 or higher
  • pnpm (recommended package manager)
  • Git for version control
  • Basic knowledge of React, TypeScript, and Tailwind CSS

Fork and Clone

  1. Fork the repository on GitHub
  2. Clone your fork:
    git clone https://github.com/YOUR_USERNAME/objectui.git
    cd objectui
  3. Add upstream remote:
    git remote add upstream https://github.com/objectstack-ai/objectui.git

Development Setup

Install Dependencies

# Install pnpm if you haven't
npm install -g pnpm

# Install project dependencies
pnpm install

Configure Git Merge Driver for pnpm-lock.yaml

To prevent merge conflicts in pnpm-lock.yaml, configure the custom merge driver:

# Set the merge driver name
git config merge.pnpm-merge.name "pnpm-lock.yaml merge driver"

# Set the merge driver command
git config merge.pnpm-merge.driver "pnpm install"

This configuration allows Git to automatically resolve conflicts in pnpm-lock.yaml by regenerating the lockfile using pnpm install instead of attempting a manual merge.

Create a Branch

# Sync with upstream
git fetch upstream
git checkout main
git merge upstream/main

# Create a feature branch
git checkout -b feature/your-feature-name

Development Workflow

Running Development Servers

# Run the visual designer demo
pnpm designer

# Run the prototype example
pnpm prototype

# Run documentation site
pnpm docs:dev

Building

# Build all packages
pnpm build

# Build specific package
cd packages/core && pnpm build

Testing

# Run all tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with UI (interactive)
pnpm test:ui

# Generate coverage report
pnpm test:coverage

Linting

# Lint all packages
pnpm lint

# Lint specific package
cd packages/react && pnpm lint

Architecture Overview

Object UI follows a modular monorepo architecture:

packages/
├── types/          # TypeScript type definitions (Zero dependencies)
├── core/           # Core logic, validation, registry (Zero React)
├── react/          # React bindings and SchemaRenderer
├── components/     # UI components (Tailwind + Shadcn)
├── designer/       # Visual schema editor
├── plugin-charts/  # Chart components plugin
└── plugin-editor/  # Rich text editor plugin

Key Principles

  1. Protocol Agnostic: Core never depends on specific backends
  2. Tailwind Native: All styling via Tailwind utility classes
  3. Type Safety: Strict TypeScript everywhere
  4. Tree Shakable: Modular imports, no monolithic bundles
  5. Zero React in Core: Core package has no React dependencies

See Architecture Documentation for details.

Writing Tests

Test Structure

All tests should be placed in __tests__ directories within the source code. We use Vitest and React Testing Library.

import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { MyComponent } from './MyComponent'

describe('MyComponent', () => {
  it('should render correctly', () => {
    render(<MyComponent />)
    expect(screen.getByText('Expected Text')).toBeInTheDocument()
  })
  
  it('should handle user interaction', async () => {
    const { user } = render(<MyComponent />)
    await user.click(screen.getByRole('button'))
    expect(screen.getByText('Clicked')).toBeInTheDocument()
  })
})

Testing Best Practices

  • Write tests for all new features
  • Test user interactions, not implementation details
  • Use meaningful test descriptions
  • Maintain or improve code coverage (current thresholds: 63% lines, 43% functions, 40% branches, 62% statements)
  • Aim to gradually increase coverage toward the long-term goal of 80%+ across all metrics
  • Test edge cases and error states

Code Style

TypeScript Guidelines

// ✅ Good: Explicit types, clear naming
interface UserData {
  id: string
  name: string
  email: string
}

function getUserById(id: string): UserData | null {
  // implementation
}

// ❌ Bad: Implicit any, unclear naming
function get(x) {
  // implementation
}

React Component Guidelines

// ✅ Good: TypeScript, named exports, clear props
interface ButtonProps {
  label: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
}

export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button 
      onClick={onClick}
      className={cn(
        'px-4 py-2 rounded',
        variant === 'primary' ? 'bg-blue-500' : 'bg-gray-500'
      )}
    >
      {label}
    </button>
  )
}

// ❌ Bad: No types, default export, inline styles
export default function Button(props) {
  return <button style={{ color: 'blue' }}>{props.label}</button>
}

Styling Guidelines

  • Always use Tailwind: Never use inline styles or CSS modules
  • Use cn() utility: For conditional classes
  • Extract repeated classes: Create reusable class combinations
  • Follow Shadcn patterns: Match the style of existing components
// ✅ Good
<div className={cn(
  'flex items-center gap-2 p-4',
  isActive && 'bg-blue-50',
  className
)}>
  {children}
</div>

// ❌ Bad
<div style={{ display: 'flex', padding: '16px' }}>
  {children}
</div>

General Guidelines

  • Use meaningful variable and function names
  • Keep functions small and focused (< 50 lines)
  • Add JSDoc comments for public APIs
  • Avoid deep nesting (max 3 levels)
  • Use early returns to reduce complexity

Commit Guidelines

We follow Conventional Commits:

Commit Types

  • feat: - New features
  • fix: - Bug fixes
  • docs: - Documentation changes
  • test: - Adding or updating tests
  • chore: - Maintenance tasks (deps, config)
  • refactor: - Code refactoring (no behavior change)
  • perf: - Performance improvements
  • style: - Code style changes (formatting)

Examples

feat: add date picker component
fix: resolve schema validation error
docs: update installation guide
test: add tests for SchemaRenderer
chore: update dependencies
refactor: simplify expression evaluator

Commit Message Format

<type>: <subject>

<body (optional)>

<footer (optional)>

Pull Request Process

Before Submitting

  1. Update from upstream:

    git fetch upstream
    git rebase upstream/main
  2. Create a changeset (for package changes):

    pnpm changeset

    This will prompt you to:

    • Select which packages have changed
    • Choose the version bump type (major, minor, patch)
    • Write a summary of the changes

    Learn more about changesets in the Versioning and Releases section.

  3. Ensure tests pass:

    pnpm test
  4. Ensure build succeeds:

    pnpm build
  5. Update documentation if needed

  6. Add tests for new features

Creating the PR

  1. Push your branch:

    git push origin feature/your-feature-name
  2. Go to GitHub and create a Pull Request

  3. Fill in the PR template:

    • Clear description of changes
    • Link to related issues
    • Screenshots for UI changes
    • Breaking changes (if any)

PR Guidelines

  • Keep PRs focused (one feature/fix per PR)
  • Write clear, descriptive PR titles
  • Include before/after screenshots for UI changes
  • Respond to review comments promptly
  • Keep commits clean and meaningful

Automated Workflows

Our repository includes several automated GitHub workflows that will run when you create a PR:

CI Pipeline

  • Linting: Checks code style and quality
  • Type Checking: Validates TypeScript types
  • Tests: Runs unit and integration tests
  • Build: Ensures all packages build successfully
  • Matrix Testing: Tests on Node.js 18.x and 20.x
  • Coverage Thresholds: Enforces minimum test coverage (see below)
Test Coverage Requirements

The project enforces minimum test coverage thresholds to maintain code quality:

  • Lines: 63% (target: gradually increase to 80%)
  • Functions: 43% (target: gradually increase to 80%)
  • Branches: 40% (target: gradually increase to 75%)
  • Statements: 62% (target: gradually increase to 80%)

These thresholds are intentionally set just below current coverage levels to prevent CI failures from minor fluctuations while we improve test coverage. New code should aim for higher coverage than these minimums.

Security Scans

  • CodeQL: Scans for security vulnerabilities in code
  • Dependency Scanning: Checks for known vulnerabilities in dependencies

PR Automation

  • Auto-labeling: Automatically labels PRs based on changed files
  • Bundle Size: Reports bundle size changes in PR comments
  • PR Checks: Validates PR requirements and posts status

What to Expect

  1. All checks must pass before merging
  2. Failed checks will show detailed error messages
  3. Some workflows (like auto-labeling) run automatically
  4. Review the check results and fix any issues

Tips for Passing Checks

  • Run pnpm lint before committing
  • Run pnpm test to catch test failures early
  • Run pnpm build to ensure successful builds
  • Keep dependencies up to date
  • Follow TypeScript strict mode requirements

Documentation

Writing Documentation

We use fumadocs for documentation. All docs are in docs/.

# Start docs dev server
pnpm site:dev

# Build docs
pnpm site:build

Documentation Guidelines

  • Use clear, concise language
  • Provide code examples for all concepts
  • Include both JSON schemas and React code
  • Use TypeScript for code examples
  • Add practical, real-world examples
  • Link to related documentation

Documentation Link Conventions

IMPORTANT: When adding internal links in documentation, follow these conventions to avoid 404 errors:

✅ Correct Link Patterns

<!-- Correct - internal documentation links MUST include /docs/ prefix -->
[Quick Start](/docs/guide/quick-start)
[Components](/docs/components)
[API Reference](/docs/reference/api/core)
[Protocol Specs](/docs/reference/protocol/overview)
[Architecture](/docs/architecture/component)

❌ Incorrect Link Patterns

<!-- Wrong - missing /docs/ prefix -->
[Quick Start](/guide/quick-start)       <!-- ❌ Should be /docs/guide/quick-start -->
[Components](/components)               <!-- ❌ Should be /docs/components -->

<!-- Wrong - incorrect paths -->
[API Reference](/api/core)              <!-- ❌ Should be /docs/reference/api/core -->
[Spec](/spec/component)                 <!-- ❌ Should be /docs/architecture/component -->
[Protocol](/protocol/form)              <!-- ❌ Should be /docs/reference/protocol/form -->

Why?

Fumadocs is configured with baseUrl: '/docs', which means all documentation pages are served under the /docs route in Next.js. Internal links must include the /docs/ prefix to match the actual URL structure where the pages are accessible.

Validating Links

Link validation runs automatically via GitHub Actions on all PRs using lychee-action. This checks for broken internal and external links.

See Documentation Guide for details.

Versioning and Releases

We use Changesets for version management and automated releases.

Understanding Changesets

Changesets is a tool that helps us:

  • Track changes: Each PR includes a changeset file describing what changed
  • Automate versioning: Automatically determine version bumps based on changesets
  • Generate changelogs: Create comprehensive changelogs from changeset descriptions
  • Coordinate releases: Release multiple packages together in our monorepo

When to Create a Changeset

Create a changeset when your PR makes changes to any package in packages/:

  • DO create a changeset for:

    • New features
    • Bug fixes
    • Breaking changes
    • Performance improvements
    • API changes
  • DON'T create a changeset for:

    • Documentation updates only
    • Changes to examples or apps
    • Internal refactoring with no user-facing changes
    • Test updates without code changes

How to Create a Changeset

  1. Run the changeset command:

    pnpm changeset
  2. Select packages: Use arrow keys and spacebar to select which packages changed

    🦋  Which packages would you like to include?
    ◯ @object-ui/core
    ◉ @object-ui/react
    ◯ @object-ui/components
    
  3. Choose version bump type:

    • Major (x.0.0): Breaking changes
    • Minor (0.x.0): New features (backwards compatible)
    • Patch (0.0.x): Bug fixes and minor updates
  4. Write a summary: Describe what changed

    Summary: Add support for custom validation rules in forms
    
  5. Commit the changeset file:

    git add .changeset/*.md
    git commit -m "chore: add changeset"

Changeset Message Guidelines

Write clear, user-facing descriptions:

✅ Good:
- Add support for custom date formats in DatePicker
- Fix validation error in nested form fields
- Improve performance of large data grids by 50%

❌ Bad:
- Updated code
- Fixed bug
- Changes to validation

Release Process

The release process is automated:

  1. Create PR with changes → Include a changeset file
  2. PR is merged → Changeset bot creates/updates a "Version Packages" PR
  3. Version PR is merged → Packages are automatically published to npm

You don't need to manually:

  • Update version numbers
  • Update CHANGELOGs
  • Create Git tags
  • Publish to npm

Everything is handled by the changeset automation!

Example Workflow

# 1. Create a feature branch
git checkout -b feat/add-date-picker

# 2. Make your changes
# ... edit files ...

# 3. Create a changeset
pnpm changeset
# Select @object-ui/components
# Choose "minor" (new feature)
# Summary: "Add DatePicker component with calendar popup"

# 4. Commit everything
git add .
git commit -m "feat: add DatePicker component"

# 5. Push and create PR
git push origin feat/add-date-picker

Adding Components

Creating a New Component

  1. Define the schema in packages/types/:

    export interface MyComponentSchema extends BaseSchema {
      type: 'my-component'
      title: string
      content: string
    }
  2. Implement the component in packages/components/:

    export function MyComponent(props: { schema: MyComponentSchema }) {
      return (
        <div className={cn('p-4', props.schema.className)}>
          <h3>{props.schema.title}</h3>
          <p>{props.schema.content}</p>
        </div>
      )
    }
  3. Register the component:

    registry.register('my-component', MyComponent)
  4. Add tests:

    describe('MyComponent', () => {
      it('should render title and content', () => {
        const schema = {
          type: 'my-component',
          title: 'Test',
          content: 'Content'
        }
        render(<SchemaRenderer schema={schema} />)
        expect(screen.getByText('Test')).toBeInTheDocument()
      })
    })
  5. Add documentation in docs/components/my-component.md

Questions & Support

Where to Ask Questions

  • GitHub Discussions - General questions and ideas
  • GitHub Issues - Bug reports and feature requests
  • Email - hello@objectui.org

How to Report Bugs

  1. Check if the bug is already reported
  2. Create a new issue with:
    • Clear title and description
    • Steps to reproduce
    • Expected vs actual behavior
    • Code examples (minimal reproduction)
    • Environment details (OS, Node version, etc.)

Feature Requests

  1. Check if it's already requested
  2. Open a discussion to gather feedback
  3. If approved, create an issue with detailed spec

License

By contributing, you agree that your contributions will be licensed under the MIT License.


Thank you for contributing to Object UI! 🎉