Thank you for contributing to the ChainForge frontend. This document covers the development workflow, code standards, and conventions for submitting changes.
- Getting started
- Development workflow
- Branching strategy
- Commit conventions
- Code standards
- Validation checklist
- Pull request process
- UI/UX guidelines
- Common tasks
- Troubleshooting
- Getting help
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 18.x | Use nvm or fnm |
| pnpm | 9.x | npm install -g pnpm or standalone installer |
| Freighter | latest | Browser extension for wallet testing |
Why pnpm? This repository uses pnpm workspaces. Using npm or yarn inside app/frontend/ directly will produce a mismatched lockfile and can break other workspace packages. Always use pnpm.
- Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/chainforge.git
cd chainforge- Install dependencies from the monorepo root:
pnpm installThis installs dependencies for all workspace packages at once using the shared lockfile. Never edit the lockfile manually.
- Set up environment variables:
cd app/frontend
cp .env.example .env.local
# Edit .env.local with your configurationKey variables to configure:
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
NEXT_PUBLIC_STELLAR_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_AID_ESCROW_CONTRACT_ID=your_contract_idThe navbar displays a network and environment indicator so you always know which Stellar network and app environment you are targeting.
- Start the dev server:
pnpm dev
# or from the monorepo root:
pnpm --filter frontend devOpen http://localhost:3000.
Always work on a branch, never directly on main:
git checkout -b feature/your-feature-name- Follow the code standards below
- Add or update tests as needed
- Update documentation if you change APIs or add features
- Run the validation checklist locally before pushing
git add .
git commit -m "feat(ui): add campaign creation dialog"git push origin feature/your-feature-name| Branch | Purpose |
|---|---|
main |
Production-ready code |
develop |
Integration branch for features |
feature/* |
New features |
bugfix/* |
Bug fixes |
hotfix/* |
Urgent production fixes |
release/* |
Release preparation |
Branch names should be descriptive and kebab-case:
# Good
feature/wallet-integration
bugfix/map-marker-positioning
# Bad
new-feature
fix
myBranchWe follow Conventional Commits.
<type>(<scope>): <description>
[optional body]
[optional footer]
| Type | Use for |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no logic change |
refactor |
Code restructuring, no feature change |
perf |
Performance improvement |
test |
Adding or updating tests |
chore |
Maintenance (deps, config) |
ci |
CI/CD changes |
Use the affected feature or area: ui, api, wallet, maps, campaign, claim, auth.
# Good
git commit -m "feat(wallet): add Freighter wallet connection"
git commit -m "fix(maps): resolve marker icon not displaying in production"
git commit -m "docs(contributing): add environment variable instructions"
# Bad
git commit -m "update stuff"
git commit -m "WIP"- Always use TypeScript. No
.jsor.jsxfiles insrc/. - Define explicit interfaces for component props and data structures.
- Avoid
any. Useunknownor a proper type. Ifanyis unavoidable, add an inline comment explaining why. - Use type inference where it does not sacrifice clarity.
// Good
interface CampaignCardProps {
title: string;
amount: number;
onClaim: () => void;
}
export function CampaignCard({ title, amount, onClaim }: CampaignCardProps) {
return <div onClick={onClick}>{title}: {amount} XLM</div>;
}- Use functional components with hooks. No class components.
- Use named exports. Default exports make refactoring harder.
- Destructure props in function parameters.
- Use fragment shorthand (
<>not<React.Fragment>).
- Use Tailwind CSS 4 utilities. Avoid custom CSS unless Tailwind cannot achieve the result.
- Never use inline
styleprops for layout or spacing. - Always support dark mode with the
dark:prefix. - Use responsive modifiers (
sm:,md:,lg:) for adaptive layouts. - For conditional class logic, use
clsxor acnhelper.
| Thing | Convention | Example |
|---|---|---|
| Component name | PascalCase | CampaignCard |
| File name | kebab-case | campaign-card.tsx |
| Hook name | camelCase with use prefix |
useCampaigns |
| Types / Interfaces | PascalCase | CampaignData |
| Constants | SCREAMING_SNAKE_CASE | API_BASE_URL |
src/components/
├── ui/ # Reusable Radix UI primitives
├── features/ # Feature-specific components
│ ├── campaign/
│ └── wallet/
└── layout/ # Layout components
- React and Next.js
- External libraries
- Internal components and utilities
- Types
- Styles
Leaflet requires a real DOM and cannot render server-side. Any component that imports from leaflet or react-leaflet must use a dynamic import with ssr: false.
const AidMap = dynamic(() => import("@/components/features/maps/aid-map"), {
ssr: false,
loading: () => <MapSkeleton />,
});- Freighter API calls are browser-only. Guard them with
typeof window !== "undefined"or call them insideuseEffect. - Never log or store a user's private key or seed phrase.
- Always target testnet during development.
| State type | Tool |
|---|---|
| Server / async state | React Query (useQuery, useMutation) |
| Local component state | useState, useReducer |
| Shared client state | React Context (if needed) |
- All client-side variables must be prefixed with
NEXT_PUBLIC_. - Document every new variable in
.env.examplewith a comment explaining its purpose. - Never commit real secrets.
.env.localis gitignored.
Run these checks locally before pushing. CI runs the same steps.
pnpm type-checkpnpm lintZero-warning policy — any warning is treated as an error.
pnpm lint --fix # Auto-fix what you canpnpm testTesting conventions:
- Co-locate test files with the source they cover
- Query by role, label, or visible text — in that order
- Wrap renders in a fresh
QueryClientProviderper test - Mock Stellar/Freighter API calls at the module boundary
pnpm buildAlways run this locally before opening a PR if you have:
- Added or changed environment variables
- Added a new dependency or changed import paths
- Modified
next.config.tsor Tailwind config - Added a new page, route, or Leaflet component
-
pnpm type-check— no errors -
pnpm lint— no warnings or errors -
pnpm test— all tests pass -
pnpm build— build succeeds - Manually tested in the dev environment
- Tested in both light and dark modes
- Tested responsive layout on mobile / tablet / desktop
- Branch is up-to-date with
develop - All validation checks pass locally
- New features include tests where applicable
- Documentation updated (README, inline comments,
.env.example) - Screenshots attached for any UI changes
## Description
Brief summary of the change and why it's needed.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Tested locally
- [ ] Added unit tests
- [ ] Tested on Stellar Testnet (for wallet/contract changes)
## Related Issues
Closes #- Automated CI runs
type-check,lint,test, andbuildon every push. - Code review — at least one maintainer reviews your PR.
- Address feedback — push changes to the same branch.
- Merge — maintainer merges once approved.
- Accessibility first — Use semantic HTML, ARIA labels, and keyboard navigation.
- Mobile-first — Design for mobile, enhance for desktop.
- Dark mode always — Every new component must support
dark:variants. - Performance — Lazy-load images, use
next/image, and usedynamicimports for heavy components.
Every interactive component should handle:
| State | What to show |
|---|---|
| Loading | Skeleton or spinner |
| Error | Clear message with a retry option |
| Empty | Helpful guidance, not a blank space |
| Destructive action | Confirmation dialog before proceeding |
- Use semantic HTML (
<button>,<nav>,<main>,<section>). - Add descriptive
alttext to all images. - Ensure color contrast meets WCAG AA.
- Test keyboard navigation (Tab, Enter, Escape, arrow keys).
- Prefer Radix UI primitives — they ship with accessibility built in.
Create a file in src/app/your-route/page.tsx. Note that Next.js App Router requires a default export for page files:
export default function CampaignsPage() {
return <main>Campaigns</main>;
}- Create the file in
src/components/ui/(primitive) orsrc/components/features/<feature>/(feature-specific). - Define a props interface.
- Implement with a named export.
export async function GET() {
return NextResponse.json({ data: "value" });
}pnpm add library-name # runtime dependency
pnpm add -D @types/library-name # types if needed- Add to
.env.examplewith a placeholder value and a comment. - Document in
README.mdunder environment setup. - Update the Vercel dashboard for staging and production.
| Problem | Solution |
|---|---|
| Hydration errors | Wrap Leaflet/Freighter components with dynamic(..., { ssr: false }) |
Freighter returns undefined |
Guard calls with typeof window !== "undefined" and use useEffect |
| ENV vars undefined | Must start with NEXT_PUBLIC_; restart dev server after changes |
| Build fails after adding Leaflet | Import with ssr: false |
| Port 3000 in use | pnpm dev -- -p 3001 |
| Stale builds | rm -rf .next && pnpm install |
- Questions: Open a GitHub Discussion
- Bugs: Open a GitHub Issue with steps to reproduce
- Docs: Check the README and project docs first
Be respectful, inclusive, and constructive. We are building this for humanitarian impact — every contribution matters.