Thanks for helping build Heliobond — a green-bond investment app on Stellar that opens green investing to everyone, from one dollar. The same values we promise users (transparency, plain language, no dark patterns, accessibility) apply to how we build: in the open, kindly, and to a high bar.
- Live demo: https://heliobond.vercel.app
- Architecture & layout: see
README.md - Code of conduct:
CODE_OF_CONDUCT.md
There's a lane for most skill sets — pick by what you enjoy:
| Lane | Examples |
|---|---|
| Frontend (React / TypeScript) | screens, components, the WebGL Helio, tests |
| Smart contracts (Rust / Soroban) | the registry + vault, wiring live reads/writes |
| Localization (no deep code needed) | translate the creator/admin/project surfaces, add a locale |
| Accessibility | WCAG audit passes, keyboard / screen-reader fixes |
| Design | extend the token system, motion, specimen cards |
| Docs | improve guides, examples, this file |
- Browse good first issues and help wanted, or this project on GrantFox.
- Claim it before you start — comment on the issue (or apply via GrantFox) so it's assigned to you and we avoid duplicate work. Every issue states its scope and acceptance criteria.
- No issue for your idea? Open a Feature request first so we can agree on scope before you build.
We don't merge unsolicited PRs that aren't tied to an accepted issue — it keeps the queue clean and your time well spent.
Prerequisites: bun 1.2.4 (the package manager / runner) and Node 18.18+.
git clone https://github.com/Heliobond/frontend.git
cd frontend
bun install
bun run dev # http://localhost:3000Useful scripts — run these before opening a PR:
bun run build # production build (must pass)
bun run typecheck # tsc --noEmit
bun run lint # ESLint
bun run format:check # Prettier — check only (used in CI)
bun run format # Prettier — rewrite files in place
bun run test # Vitest unit + component test suite
bun run test:e2e # Playwright end-to-end tests
bun run start # serve the production buildThe project uses Vitest with a jsdom environment and
@testing-library/react
for component rendering (config: vitest.config.mts, vitest.setup.ts).
bun run test # run all tests once and exit
bun run test:ui # open the Vitest browser UIStructure. Unit and component tests are co-located with the code they
cover, as <Name>.test.ts / <Name>.test.tsx next to <Name>.ts(x) — e.g.
src/components/Button.test.tsx, src/wallet/vault.test.ts,
src/hooks/useSessionTimeout.test.ts. Tests that cover cross-cutting behaviour
rather than a single module (i18n catalog parity, shared bond math, contrast
ratios) live in src/__tests__/ instead. Vitest picks up anything matching
**/*.test.{ts,tsx}, so a new test file just needs the right name and location
to be included automatically.
Helpers. A shared render helper lives in src/test/render.tsx. It wraps
components in the LocaleProvider (i18n) and ThemeProvider the app uses at
runtime, so component tests get a realistic context instead of a bare tree.
Import render (and re-exported @testing-library/react utilities like
screen, fireEvent) from there instead of from @testing-library/react
directly:
import { render, screen, fireEvent } from '@/test/render'
test('renders the primary label', () => {
render(<Button variant="primary">Continue</Button>)
expect(screen.getByRole('button', { name: 'Continue' })).toBeVisible()
})If a test needs next-intl strings, they come from messages/en.json via the
helper's LocaleProvider — no extra setup required. Add new unit tests next to
the code under test using this pattern; there's no separate mocking layer to
configure beyond what vitest.setup.ts already provides.
Playwright drives a real Chromium browser against the
running Next.js dev server (config: playwright.config.ts — single Chromium
project, dev server started automatically unless one is already running).
bun run test:e2e # headless Chromium (starts dev server automatically)Structure. E2E specs live in e2e/ as <flow>.spec.ts (e.g.
e2e/deposit.spec.ts), one file per user-facing flow, grouped with
test.describe. There's no page-object layer yet — specs query the DOM
directly via Testing-Library-style locators (page.getByRole(...),
page.getByText(...)).
Helpers. Because the wallet integration needs a real browser extension,
specs seed a demo session via page.addInitScript before navigating, so the
flow under test never depends on an actual Stellar wallet:
async function seedDemoWallet(page: Page) {
await page.addInitScript(
({ address }) => {
localStorage.setItem('hb-address', address)
localStorage.setItem('hb-wallet', 'demo')
},
{ address: DEMO_ADDRESS },
)
}Follow e2e/deposit.spec.ts as the template for a new flow: seed whatever
session state the flow needs, page.goto() the route, then assert each step
of the flow in order with expect(locator).toBeVisible() /
toBeDisabled().
- Branch off
main:git checkout -b <type>/<short-description>(e.g.feat/withdraw-max-chip,fix/helio-glow,i18n/creator-screens). - Make focused changes — one issue per PR.
- Run the checks locally:
bun run build(must pass),bun run typecheck,bun run lint,bun run format:check, andbun run test. - If your change is user-facing or otherwise notable (a feature, a fix, a
breaking change), add an entry under
[Unreleased]inCHANGELOG.md— see that file's "How entries are added" section for the format. Purely internal changes (refactors, tooling, formatting) don't need one. - Open a PR using the template; link the issue with
Closes #123. - CI runs build, typecheck, lint, and format check on every PR;
mainis protected and requires green CI plus a maintainer review before merge.
CODEOWNERS requires maintainer review for sensitive areas — the wallet integration, design tokens, i18n catalogs, and CI.
Heliobond uses next-intl with cookie-based locale
selection. Message catalogs live at messages/en.json and messages/fr.json;
the request config in src/i18n/request.ts loads the matching catalog for the
current locale.
When you add or change user-facing copy:
- Pick the namespace that matches the surface using the copy, such as
Nav,Footer,Landing,Deposit, orProjectDetail. - Add the same key path to both
messages/en.jsonandmessages/fr.json. The catalogs must stay in parity: every namespace and key in English must also exist in French, and vice versa. - Translate the value in each catalog. Do not leave English placeholder text in
fr.jsonunless the issue explicitly calls for a temporary fallback. - Read the key from code with
useTranslations('<Namespace>'), then callt('<key>'). For example:
import { useTranslations } from 'next-intl'
export function Example() {
const t = useTranslations('Creator')
return <h1>{t('title')}</h1>
}To add a new namespace for a new screen or surface:
- Create the namespace object in both catalogs with identical keys:
{
"Creator": {
"title": "Build your project"
}
}- Add the translated French values under the same namespace and key names in
messages/fr.json. - Use that namespace from the component with
useTranslations('Creator'). - Run
bun run buildorbun run typecheckbefore opening the PR so missing or misspelled message keys are caught with the rest of the app checks.
- Builds and type-checks clean.
bun run buildis the gate; noanyto paper over types, no@ts-ignorewithout a comment. - Follow the design system. Use the CSS custom properties (
var(--ink),var(--solar), …) — never hardcode colours. Honour the brand rules: sentence case (no all-caps headlines), mono tabular numerals for figures, every delta carries a +/− sign and arrow (colour is never the sole carrier), solar is never text on a light background and never the only carrier of meaning, no emoji in the product, no exclamation marks on financial copy. SeeREADME.mdandsrc/styles/tokens/. - User-facing strings are translated. If you add or change copy in the shell or translated screens, add the key to both
messages/en.jsonandmessages/fr.json(they must stay in parity). - Accessibility is not optional. Keyboard operable, visible focus, semantic landmarks,
prefers-reduced-motionrespected, touch targets ≥ 44px. - No secrets in the repo or in client code.
- The issue's acceptance criteria are met.
- CI is green; the PR is reviewed and approved.
- UI changes include before/after screenshots (or a short screencast).
- Docs/translations updated where relevant.
- Bugs: open a Bug report issue with steps to reproduce.
- Security: please do not open a public issue. Use GitHub's "Report a vulnerability" (Security tab) for a private advisory.
The project ships a pre-commit hook via Husky that runs the TypeScript type-checker, ESLint, Prettier format check, and the full test suite on every commit. Install it:
bun run prepareTo opt out, skip the prepare step — the hook is not installed unless you run
it. Contributors who opt out are still expected to run bun run build before opening a PR.
By contributing, you agree to abide by the Code of Conduct.