Skip to content

Latest commit

 

History

History
441 lines (324 loc) · 14.5 KB

File metadata and controls

441 lines (324 loc) · 14.5 KB

Contributing to conduit-app

Thank you for helping improve the Conduit web interface. This guide covers setup, design rules, component conventions, and the PR process.


Table of Contents

  1. Code of Conduct
  2. Getting Started
  3. Repository Layout
  4. Development Workflow
  5. Design System
  6. Component Conventions
  7. Contract Integration
  8. Testing
  9. Commit Convention
  10. Pull Request Process

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. By participating you agree to uphold it. Report unacceptable behaviour to conduct@conduit.sh.


Getting Started

Prerequisites

Tool Version
Node.js ≥ 20
npm ≥ 10
A Stellar wallet Freighter, xBull, Albedo, or Hana

Setup

git clone https://github.com/conduit-protocol/conduit-app
cd conduit-app
npm install

# Copy environment variables
cp .env.example .env.local

Edit .env.local:

NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
NEXT_PUBLIC_FACTORY_CONTRACT_ID=C...     # from conduit-contracts deploy
NEXT_PUBLIC_GOVERNOR_CONTRACT_ID=C...    # from conduit-contracts deploy
NEXT_PUBLIC_HORIZON_URL=https://horizon-testnet.stellar.org

Don't have deployed contract IDs? Use the testnet deployments listed in the conduit-contracts releases.

Start the development server:

npm run dev
# → http://localhost:3000

Type-check and lint

npm run typecheck   # tsc --noEmit
npm run lint        # ESLint
npm test            # Vitest unit tests
npm run build       # production build (catches Next.js-specific errors)

Repository Layout

conduit-app/
├── app/                         # Next.js App Router pages
│   ├── layout.tsx               # Root layout — Navbar, Providers, footer
│   ├── page.tsx                 # Landing page
│   ├── (marketing)/
│   │   └── about/page.tsx       # Protocol explainer
│   ├── streams/page.tsx         # Stream list (receiving + sending tabs)
│   ├── stream/[id]/page.tsx     # Single stream view + actions
│   ├── create/page.tsx          # Create stream form
│   └── dashboard/page.tsx       # Aggregate sender stats
├── components/
│   ├── ui/                      # Primitive design system components
│   │   ├── README.md            # ← read this before adding UI components
│   │   ├── Button.tsx
│   │   ├── Card.tsx
│   │   ├── Input.tsx
│   │   ├── Badge.tsx
│   │   ├── ProgressBar.tsx
│   │   └── Modal.tsx
│   ├── stream/                  # Stream-specific composed components
│   │   ├── StreamCard.tsx       # Summary card used in list
│   │   ├── StreamActions.tsx    # Role-gated action buttons
│   │   ├── StreamTimeline.tsx   # Horizontal progress timeline
│   │   ├── RateTicker.tsx       # Live per-second counter
│   │   └── WithdrawButton.tsx   # Withdraw with pending state
│   ├── ConnectButton.tsx        # Freighter connect trigger (via WalletContext)
│   ├── Navbar.tsx
│   └── Providers.tsx            # Context providers tree
├── contexts/
│   └── WalletContext.tsx        # Wallet state, address, signTransaction
├── lib/
│   ├── soroban.ts               # Soroban RPC client + helpers
│   ├── factory.ts               # DripFactory call wrappers
│   ├── stream.ts                # DripStream call wrappers
│   ├── tokens.ts                # Known Stellar asset list
│   └── format.ts                # Amount formatting, relative time
├── tailwind.config.ts
├── next.config.ts
├── tsconfig.json
└── .env.example

Development Workflow

main          ← always deployable
  └── feat/your-feature
  1. Fork and clone your fork.
  2. Create a branch:
    git checkout -b feat/my-feature
  3. Make changes with frequent npm run typecheck checks.
  4. Test in the browser — connect a Freighter wallet on testnet and walk through the flow you changed.
  5. Run the full check suite:
    npm run typecheck && npm run lint && npm test && npm run build
  6. Push and open a PR.

Design System

The Conduit app is black and white only. This is a hard constraint, not a preference.

Allowed colour utilities

text-black    text-white    text-gray-{50..950}
bg-black      bg-white      bg-gray-{50..950}
border-black  border-white  border-gray-{50..950}
ring-black    ring-white    ring-gray-{50..950}

Prohibited

text-blue-*   text-red-*   text-green-*   text-indigo-*
bg-blue-*     bg-red-*     bg-emerald-*   bg-violet-*
Any Tailwind hue-named colour class

Exceptions (must use aria-label)

text-green-600 for positive balance deltas and text-red-600 for negative deltas are permitted only when accompanied by an aria-label so colour is never the sole signal:

<span className="text-green-600 font-mono" aria-label="increase">
  +{fromStroops(delta)} XLM
</span>

Typography rules

  • Numbers and addresses: always font-mono tabular-nums
  • Section headings: font-black tracking-tight
  • Labels and metadata: text-xs text-gray-400
  • Borders: prefer border-gray-100 for containers, border-black for interactive focus states

Spacing

  • Page containers: max-w-3xl mx-auto px-4
  • Vertical rhythm between sections: mb-10 or mb-16
  • Card padding: p-4 or p-6

See components/ui/README.md for the full component API.


Component Conventions

Server vs Client components

Next.js App Router defaults to Server Components. Use 'use client' only when the component needs:

  • Browser APIs (window, navigator, requestAnimationFrame)
  • React state (useState, useEffect, useReducer)
  • Event handlers
  • Context consumers

Keep data fetching in Server Components and push interactivity as far down the tree as possible.

Naming

Pattern Example
Page components app/streams/page.tsx → default export
UI primitives components/ui/Button.tsx → named export Button
Feature components components/stream/StreamCard.tsx → named export StreamCard
Hooks hooks/useStream.ts → named export useStream
Lib helpers lib/format.ts → named exports

Props interfaces

Define prop types as named interfaces directly above the component:

interface StreamCardProps {
  id:            string;
  counterparty:  string;
  role:          'sender' | 'recipient';
  // ...
}

export function StreamCard({ id, counterparty, role }: StreamCardProps) {
  // ...
}

Do not use inline React.FC<{...}> — it hides the component name in stack traces.

Loading states

Every data-fetching component must handle a loading state. Use a skeleton that mirrors the shape of the loaded content:

if (loading) {
  return (
    <div className="card animate-pulse">
      <div className="h-4 bg-gray-100 rounded w-1/2 mb-2" />
      <div className="h-3 bg-gray-100 rounded w-1/3" />
    </div>
  );
}

Error states

Display errors inline, never alert(). Use a simple bordered container:

if (error) {
  return (
    <div className="border border-gray-200 p-4 text-sm text-gray-500">
      {error.message}
    </div>
  );
}

Contract Integration

All Soroban calls go through lib/soroban.ts and the wrappers in lib/stream.ts / lib/factory.ts. Do not import @stellar/stellar-sdk directly in page or component files.

Read-only calls (simulation only):

// lib/stream.ts
export async function getWithdrawable(source: string, streamAddress: string): Promise<bigint>
export async function getStreamInfo(source: string, streamAddress: string): Promise<StreamInfo>

Mutating calls (require signTx from WalletContext):

// lib/stream.ts — signTx is (xdrBase64: string) => Promise<string>
export async function withdraw(sender, streamAddress, amount, signTx): Promise<string>
export async function cancel(sender, streamAddress, signTx): Promise<string>

Transactions are assembled client-side via Soroban simulation, signed by the user's wallet, and submitted to the RPC. Never hard-code a secret key.


Testing

We use Vitest for unit tests. Integration / E2E tests are not yet in scope.

What to test

  • Pure utility functions in lib/format.ts and lib/tokens.ts
  • Any non-trivial conditional logic in components (test the logic, not the rendering)

What not to test

  • Styling — visual review in the browser
  • Soroban RPC calls — mock at the lib/soroban.ts boundary

Running tests

npm test                       # run once
npm run test:watch             # watch mode
npx vitest run --coverage      # with coverage report

Commit Convention

We follow Conventional Commits:

<type>(<scope>): <short description>

Types: feat, fix, refactor, style, test, docs, chore, perf

Scopes: dashboard, streams, stream, create, ui, wallet, lib, layout, ci, deps

Examples:

feat(dashboard): add aggregate flow rate stat card

fix(stream): handle cancelled state in RateTicker — return 0 immediately

refactor(ui): extract SkeletonCard into shared component

style(create): align form labels with 12px grid

test(lib): add edge cases for fromStroops with zero and max i128

chore(deps): bump next to 15.2.1

Pull Request Process

Branch naming

feat/<issue-number>-short-slug      # new feature or UI
fix/<issue-number>-short-slug       # bug fix
refactor/<issue-number>-short-slug  # component refactor
test/<issue-number>-short-slug      # tests only
docs/<issue-number>-short-slug      # docs only
style/<issue-number>-short-slug     # visual / design only

Examples: fix/1-start-time-buffer, feat/5-force-cancel-ui

5-commit convention

Every PR must contain at least 5 commits. They must follow this logical order — reviewers read them in sequence:

# Commit type What it contains
1 test(<scope>): add unit test for <issue> Tests for the logic being changed — written first. UI logic tests live in lib/ or alongside components. Expected to fail (or not exist) before the fix.
2 fix(<scope>) or feat(<scope>): core implementation The minimal change to make tests pass: the logic fix, new hook, or new contract call. No styling in this commit.
3 feat(<scope>): UI components and layout Rendering layer: the JSX, props, conditional display. No business logic in this commit.
4 style(<scope>): visual polish and accessibility Tailwind classes, aria attributes, loading skeletons, responsive adjustments. No logic.
5 chore(<scope>): typecheck + lint pass Fix any TypeScript or ESLint issues surfaced by the change. No functional changes.

Rules:

  • Every commit body must explain why this change is needed, not just what it does.
  • Reference the issue in the core implementation commit: Closes #1.
  • No merge commits or fixup! commits in the branch — rebase and amend before review.
  • Commits 2 and 3 must be separately git cherry-pick-able (no styling mixed into logic commits).

Example commit sequence for fix/1-start-time-buffer

test(create): add test — streams with <300s buffer fail BackdatedStream

fix(create): increase startTime buffer to 300s to cover tx inclusion lag

feat(create): show user-friendly error message for BackdatedStream failures

style(create): add buffer-time note to duration helper text

chore(create): typecheck and lint clean after create page changes

Author checklist before opening a PR

  • Branch name follows the naming convention above
  • PR title: fix(create): increase start time buffer to 300s (#1)
  • PR body includes Closes #<n> or Fixes #<n>
  • At least 5 commits, each with a body explaining why
  • npm run typecheck — no errors
  • npm run lint — no warnings
  • npm test — all tests pass
  • npm run build — production build succeeds
  • Tested in the browser with a connected Freighter wallet on testnet (describe what you clicked)
  • No hue-named colour classes introduced (exception: text-red-600 / text-green-600 with aria-label)
  • Loading and error states handled for any new data-fetching UI
  • CHANGELOG.md entry under [Unreleased]
  • Before/after screenshots included for any visible UI change

Review requirements

  • Mandatory owner review: Every PR requires approval from @jaydbrown before it can be merged. This applies to all PRs — including docs, style, and chore PRs.
  • PRs changing lib/soroban.ts or contexts/WalletContext.tsx additionally require 1 further maintainer approval (2 approvals total) — these are critical security boundaries.
  • CI must be green (typecheck, lint, tests, build).

Reviewer checklist

  • Commit 1 is a test that fails on main (or demonstrates the missing coverage)
  • Logic commit (commit 2) contains no Tailwind class changes
  • UI commit (commit 3) contains no business logic
  • No hue-named colour classes without an aria-label
  • All data-fetching UI has a loading and error state
  • No Number() on large bigint values
  • No any types in TypeScript
  • No direct imports of @stellar/stellar-sdk in page or component files (only lib/)
  • Wallet address validation uses StrKey.isValidEd25519PublicKey() (not length-only) — this is the standard going forward; existing regex-based validation in TokenSelector.tsx, BulkWithdrawButton.tsx, and BatchStreamCreator.tsx predates this checklist item and is tracked separately for migration, not exempted from it. New/changed validation code must meet this bar.
  • 5-commit minimum is met and commits are in logical order
  • Screenshots attached for visible UI changes

Screenshots

For any visible UI change, include before/after screenshots in the PR description.


License

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