Thank you for your interest in contributing to Protocol Guide! This document provides guidelines and instructions for contributing.
- Code of Conduct
- Getting Started
- Development Workflow
- Code Style Guide
- Testing Requirements
- Pull Request Process
- Commit Message Guidelines
This project adheres to a code of conduct. By participating, you are expected to uphold this code:
- Be respectful and inclusive
- Exercise empathy and kindness
- Give and accept constructive feedback gracefully
- Focus on what is best for the community
- Node.js 20+
- pnpm (recommended) or npm
- PostgreSQL (for local development)
- Git
-
Fork and clone the repository
git clone https://github.com/YOUR-USERNAME/Protocol-Guide.git cd Protocol-Guide -
Install dependencies
pnpm install
-
Set up environment variables
cp .env.example .env.local # Edit .env.local with your credentials -
Run database migrations
pnpm db:push
-
Start development server
pnpm dev
Use descriptive branch names with prefixes:
feature/- New features (feature/voice-search-improvements)fix/- Bug fixes (fix/search-cache-invalidation)docs/- Documentation updates (docs/api-endpoints)refactor/- Code refactoring (refactor/auth-flow)test/- Test additions/fixes (test/search-router-coverage)chore/- Maintenance tasks (chore/upgrade-dependencies)
- Create a branch from
main - Make your changes
- Write/update tests
- Ensure all tests pass
- Submit a pull request
- File Size: Maximum 500 lines per file
- Single Responsibility: Each file/function should do one thing well
- Explicit Types: No
anytypes in TypeScript - Documentation: Add JSDoc comments to public APIs
// ✅ Good: Explicit types, clear naming
interface SearchParams {
query: string;
limit: number;
agencyId?: number;
}
async function searchProtocols(params: SearchParams): Promise<SearchResult[]> {
// Implementation
}
// ❌ Bad: Implicit types, unclear naming
async function search(p: any) {
// Implementation
}// ✅ Good: Functional component with typed props
interface SearchBarProps {
onSearch: (query: string) => void;
placeholder?: string;
disabled?: boolean;
}
export function SearchBar({ onSearch, placeholder, disabled }: SearchBarProps) {
// Implementation
}
// ❌ Bad: Missing types, class component
export default class SearchBar extends Component {
// Implementation
}// ✅ Good: Prefixed with use-, typed return
export function useProtocolSearch(): UseProtocolSearchResult {
const [isLoading, setIsLoading] = useState(false);
// Implementation
return { isLoading, search, results };
}// ✅ Good: Proper input validation, error handling
export const searchRouter = router({
semantic: publicRateLimitedProcedure
.input(z.object({
query: z.string().min(1).max(500),
limit: z.number().min(1).max(50).default(10),
}))
.query(async ({ input, ctx }) => {
try {
// Implementation
} catch (error) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Search failed',
cause: error,
});
}
}),
});// ✅ Good: Co-located, focused files
components/
search/
SearchBar.tsx # Component
SearchBar.test.tsx # Tests
use-search.ts # Hook
types.ts # Types
// ❌ Bad: Scattered files
components/SearchBar.tsx
hooks/useSearch.ts
types/searchTypes.ts
__tests__/SearchBar.test.tsx
| Type | Convention | Example |
|---|---|---|
| Files | kebab-case | use-auth.ts |
| Components | PascalCase | SearchBar |
| Functions | camelCase | handleSearch |
| Constants | UPPER_SNAKE | MAX_RESULTS |
| Types/Interfaces | PascalCase | SearchResult |
| Hooks | use- prefix | useAuth |
Use NativeWind (Tailwind CSS for React Native):
// ✅ Good: Tailwind classes via className
<View className="flex-1 bg-white p-4">
<Text className="text-lg font-bold text-gray-900">
Protocol Guide
</Text>
</View>
// ❌ Bad: Inline styles
<View style={{ flex: 1, backgroundColor: 'white', padding: 16 }}>- Server utilities: 80%+ coverage
- React hooks: 70%+ coverage
- tRPC routers: 70%+ coverage
- Critical paths: 90%+ coverage
import { describe, it, expect, vi } from 'vitest';
import { normalizeEmsQuery } from '../ems-query-normalizer';
describe('normalizeEmsQuery', () => {
it('expands common EMS abbreviations', () => {
const result = normalizeEmsQuery('epi dose for anaphylaxis');
expect(result.normalized).toContain('epinephrine');
});
it('handles typos', () => {
const result = normalizeEmsQuery('cardiack arrest');
expect(result.correctedTypos).toContain('cardiac');
});
});import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestContext } from './helpers';
describe('Search Router', () => {
let ctx: TestContext;
beforeAll(async () => {
ctx = await createTestContext();
});
afterAll(async () => {
await ctx.cleanup();
});
it('returns relevant protocols for valid query', async () => {
const result = await ctx.caller.search.semantic({
query: 'cardiac arrest',
limit: 5,
});
expect(result.results.length).toBeGreaterThan(0);
});
});import { test, expect } from '@playwright/test';
test('user can search for protocols', async ({ page }) => {
await page.goto('/');
await page.fill('[data-testid="search-input"]', 'chest pain');
await page.click('[data-testid="search-button"]');
await expect(page.locator('[data-testid="search-results"]'))
.toBeVisible();
await expect(page.locator('[data-testid="result-item"]'))
.toHaveCount({ min: 1 });
});# Unit tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage report
pnpm test:coverage
# Integration tests
pnpm test:integration
# E2E tests
pnpm test:e2e
# All tests
pnpm test:all- Update documentation if your changes affect public APIs
- Add tests for new functionality
- Run the full test suite:
pnpm test:all - Run type checking:
pnpm check - Run linting:
pnpm lint - Update CHANGELOG.md if applicable
Your PR description should include:
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] E2E tests added/updated (if applicable)
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warnings introduced- PRs require at least one approval
- All CI checks must pass
- Resolve all review comments
- Squash and merge is preferred
Follow Conventional Commits:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no code change |
refactor |
Code change that neither fixes a bug nor adds a feature |
perf |
Performance improvement |
test |
Adding or fixing tests |
chore |
Maintenance tasks |
# Feature
feat(search): add voice input support
# Bug fix
fix(auth): resolve token refresh race condition
# Documentation
docs(api): update search endpoint documentation
# Breaking change
feat(api)!: change search response format
BREAKING CHANGE: search results now include `fullContent` fieldIf you have questions about contributing:
- Check existing issues and discussions
- Open a new issue with the
questionlabel - Reach out to maintainers
Thank you for contributing to Protocol Guide! 🚑