Thank you for your interest in contributing to Object UI! This document provides guidelines and instructions for contributing to the project.
- Getting Started
- Development Setup
- Development Workflow
- Architecture Overview
- Writing Tests
- Code Style
- Commit Guidelines
- Pull Request Process
- Documentation
- Adding Components
- Questions & Support
- Node.js 18.0 or higher
- pnpm (recommended package manager)
- Git for version control
- Basic knowledge of React, TypeScript, and Tailwind CSS
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/objectui.git cd objectui - Add upstream remote:
git remote add upstream https://github.com/objectstack-ai/objectui.git
# Install pnpm if you haven't
npm install -g pnpm
# Install project dependencies
pnpm installTo 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.
# Sync with upstream
git fetch upstream
git checkout main
git merge upstream/main
# Create a feature branch
git checkout -b feature/your-feature-name# Run the visual designer demo
pnpm designer
# Run the prototype example
pnpm prototype
# Run documentation site
pnpm docs:dev# Build all packages
pnpm build
# Build specific package
cd packages/core && pnpm build# 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# Lint all packages
pnpm lint
# Lint specific package
cd packages/react && pnpm lintObject 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
- Protocol Agnostic: Core never depends on specific backends
- Tailwind Native: All styling via Tailwind utility classes
- Type Safety: Strict TypeScript everywhere
- Tree Shakable: Modular imports, no monolithic bundles
- Zero React in Core: Core package has no React dependencies
See Architecture Documentation for details.
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()
})
})- 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
// ✅ 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
}// ✅ 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>
}- 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>- 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
We follow Conventional Commits:
feat:- New featuresfix:- Bug fixesdocs:- Documentation changestest:- Adding or updating testschore:- Maintenance tasks (deps, config)refactor:- Code refactoring (no behavior change)perf:- Performance improvementsstyle:- Code style changes (formatting)
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<type>: <subject>
<body (optional)>
<footer (optional)>
-
Update from upstream:
git fetch upstream git rebase upstream/main
-
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.
-
Ensure tests pass:
pnpm test -
Ensure build succeeds:
pnpm build
-
Update documentation if needed
-
Add tests for new features
-
Push your branch:
git push origin feature/your-feature-name
-
Go to GitHub and create a Pull Request
-
Fill in the PR template:
- Clear description of changes
- Link to related issues
- Screenshots for UI changes
- Breaking changes (if any)
- 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
Our repository includes several automated GitHub workflows that will run when you create a PR:
- 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)
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.
- CodeQL: Scans for security vulnerabilities in code
- Dependency Scanning: Checks for known vulnerabilities in dependencies
- 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
- All checks must pass before merging
- Failed checks will show detailed error messages
- Some workflows (like auto-labeling) run automatically
- Review the check results and fix any issues
- Run
pnpm lintbefore committing - Run
pnpm testto catch test failures early - Run
pnpm buildto ensure successful builds - Keep dependencies up to date
- Follow TypeScript strict mode requirements
We use fumadocs for documentation. All docs are in docs/.
# Start docs dev server
pnpm site:dev
# Build docs
pnpm site:build- 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
IMPORTANT: When adding internal links in documentation, follow these conventions to avoid 404 errors:
<!-- 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)<!-- 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 -->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.
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.
We use Changesets for version management and automated releases.
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
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
-
Run the changeset command:
pnpm changeset
-
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 -
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
-
Write a summary: Describe what changed
Summary: Add support for custom validation rules in forms -
Commit the changeset file:
git add .changeset/*.md git commit -m "chore: add changeset"
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 validationThe release process is automated:
- Create PR with changes → Include a changeset file
- PR is merged → Changeset bot creates/updates a "Version Packages" PR
- 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!
# 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-
Define the schema in
packages/types/:export interface MyComponentSchema extends BaseSchema { type: 'my-component' title: string content: string }
-
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> ) }
-
Register the component:
registry.register('my-component', MyComponent)
-
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() }) })
-
Add documentation in
docs/components/my-component.md
- GitHub Discussions - General questions and ideas
- GitHub Issues - Bug reports and feature requests
- Email - hello@objectui.org
- Check if the bug is already reported
- 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.)
- Check if it's already requested
- Open a discussion to gather feedback
- If approved, create an issue with detailed spec
By contributing, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing to Object UI! 🎉