Skip to main content

Before you start

  1. Get the app running: Run the app locally
  2. Skim the architecture guide, especially the data layer
  3. Find your feature in Features & Routes

Where does my code go?

Start inside a feature. Promote to shared/ on the second use, not the first.

Recipe: add a new API call

1

Add the service function

Services are plain async functions built on the shared client. Use apiData when the endpoint returns a single resource, apiRequest when you need the pagination envelope too.
Always accept and forward context. It carries the abort signal and, server-side, cookies.
2

Add a query key

Nesting under detail(projectId) means invalidating the project also invalidates its contributors.
3

Add the hook

4

Use it in a component

Components call hooks. They never call services or fetch directly.
5

Mock the endpoint

Local development will return 501 until the mock knows the route. See the Mock API guide.

Recipe: add a mutation

Invalidate everything the write affected, including keys owned by other features, as here where bookmarking a project changes the profile’s bookmark list. Surface failures to the user with getErrorMessage() from src/shared/lib/get-error-message.ts and a sonner toast, which is the pattern the rest of the app follows.

Recipe: add a page

1

Create the route

Keep route files thin: resolve params, render a view.
2

Create the view

The view is a client component in the feature that calls the hooks and composes components: src/features/projects/views/contributors.view.tsx.
3

Protect it if needed

Add the path to the protected list in src/middleware.ts, and to the disallow list in src/app/robots.ts if it shouldn’t be indexed.

Recipe: add a UI component

Check src/shared/components/ui/ first. There are already 60-odd components covering uploads, comboboxes, cards, tables, pagination, and markdown rendering. For a new shadcn primitive:
Compose classes with cn(), and use design tokens rather than raw colours:
New design tokens go in src/app/globals.css. There is no tailwind.config.js.

Style rules that will fail your build

Biome runs in CI, and a few rules surprise people:
Use a union type or a const object:
80-column lines, 2-space indent, double quotes, ES5 trailing commas, semicolons always. pnpm lint fails on drift. Run pnpm lint:write.
Biome’s organizeImports assist sorts imports. Don’t reorder them by hand; let pnpm lint:write do it.
noInferrableTypes, noParameterAssign, noUselessElse, useAsConstAssertion, useDefaultParameterLast, useSingleVarDeclarator, useNumberNamespace.noUnusedVariables and useExhaustiveDependencies are warnings. They won’t fail CI, but fix them anyway.
Several accessibility rules are disabled project-wide. Lint passing does not mean your component is accessible. Check keyboard navigation and labelling yourself.

Before you push

That’s exactly what CI runs. The last two catch build-time errors that type-check won’t.

Contribution workflow

Branches, commits, and opening the pull request.