---
title: "Repo & conventions"
description: "How the Lopifi repo is laid out, how files and tokens are named, the shape every component follows, and the tests, commands and contribution rules a change has to pass."
canonical: "https://lopifi.com/docs/architecture/repo-and-conventions"
---

# Repo & conventions

> How the Lopifi repo is laid out, how files and tokens are named, the shape every component follows, and the tests, commands and contribution rules a change has to pass.

Source: https://lopifi.com/docs/architecture/repo-and-conventions
Part of: Lopifi — Planned: US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

# Repo & conventions

How the Lopifi repo is laid out, how files and tokens are named, the shape every component follows, and the tests, commands and contribution rules a change has to pass.

```
finance-blocks/
├── README.md
├── DESIGN.md                       ← machine-readable token contract
├── registry.json
├── docs/                           ← the specs; source of truth for behavior
│   └── catalog/index.md         the component manifest the generators parse
│
├── src/
│   ├── ledger/                     the components, as they'll be copied
│   │   ├── tokens.css              the single source of token truth
│   │   ├── preset.ts               Tailwind v3 preset (ships with @lopifi/theme)
│   │   ├── theme-v4.css            Tailwind v4 @theme
│   │   ├── lib/                    money.types.ts, format-money.ts, allocate.ts,
│   │   │                           fiscal-period.ts, cn.ts, …
│   │   ├── hooks/                  use-density.ts, use-live-value.ts,
│   │   │                           use-column-priority.ts, …
│   │   ├── primitives/             the only files that import Base UI
│   │   └── components/             money.tsx, …
│   ├── lib/*.generated.json        build output, never edited by hand
│   ├── tools/                      the toolbelt: lib/ arithmetic, specs/, spec.ts
│   ├── playground/                 the workbook's grid and sheets
│   ├── learn/                      the course
│   └── app/                        Next.js App Router: docs site, llms.txt, registry, MCP server
│
├── extension/                      the Chrome extension (MV3)
├── tests/                          Vitest
├── public/r/                       the published registry
│
└── scripts/
    ├── build-catalog.mjs           catalog, docs index
    ├── build-glossary.mjs          glossary from the lexicon and metrics docs
    ├── build-registry.mjs          registry items from the imports
    └── build-*.mjs                 the other generators, all run by `bun run gen`
```

---

## Naming

| Thing | Convention | Example |
|---|---|---|
| Files | kebab-case | `balance-card.tsx` |
| Components | PascalCase | `BalanceCard` |
| Hooks | `use-{name}.ts` | `use-density.ts` |
| Props types | `{Component}Props` | `BalanceCardProps` |
| Constants | UPPER_SNAKE | `DEFAULT_DENSITY` |
| CSS vars | kebab, semantic-first | `--text-secondary`, not `--gray-600` |
| Registry items | kebab, matching the file | `balance-card` |

**Tokens are named by role, never by value.** Write `--text-secondary` instead of `--gray-600` and `--positive` instead of `--green`. You can't re-theme a token named after its value, and renaming it later is a breaking change.

---

## File conventions

- **Named exports only.** No default exports and no barrel files. Direct imports keep the registry's file graph accurate and tree-shaking predictable.
- **One component per file.** Sub-parts that are never used on their own live in the same file (`Card`, `CardHeader`, `CardTitle` in `card.tsx`).
- **`"use client"` only when interaction demands it.** `Money`, `Figure`, `StatementRow` and `Rule` render in bulk, so they stay server-safe.
- **Every component has a spec in `docs/catalog/`** with the four layers from [component anatomy](https://lopifi.com/md/docs/craft/component-anatomy). The registry and the docs site are generated from it, so a consumer who installs the component can read the reasoning next to it.
- **Imports:** `@/*` → `./src/*`. Consumers get `@/components/ledger/*`.

---

## Component skeleton

```tsx
"use client";

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/ledger/cn";

const balanceCardVariants = cva(
  "relative rounded-xl border border-[--border] bg-[--surface] p-6",
  {
    variants: {
      size: { sm: "p-4", md: "p-6", lg: "p-6" },
      interactive: {
        true: "transition-transform duration-1 ease-out active:scale-[0.97] hover:-translate-y-px hover:shadow-[--elevation-1]",
        false: "",
      },
    },
    defaultVariants: { size: "md", interactive: false },
  },
);

export interface BalanceCardProps
  extends React.ComponentPropsWithoutRef<"div">,
    VariantProps<typeof balanceCardVariants> {
  value: Money | null;
  polarity: Polarity; // required, no default
  action?: React.ReactNode;
}

export const BalanceCard = React.forwardRef<HTMLDivElement, BalanceCardProps>(
  ({ className, size, interactive, value, polarity = "standard", action, ...props }, ref) => (
    <div ref={ref} className={cn(balanceCardVariants({ size, interactive }), className)} {...props}>
      {/* … */}
    </div>
  ),
);
BalanceCard.displayName = "BalanceCard";
```

Every primitive: `cva` for variants, `forwardRef`, `displayName`, `cn()` merging a `className`, props spread to the root, extends the native element's props.

---

## Testing

| Layer | Tool | What |
|---|---|---|
| Unit | Vitest | Formatters, allocation, fiscal periods. **Property-based tests** on money math over random amounts and currencies (planned; today these are example-based). |
| Component | Vitest + Testing Library (planned, not built) | Every state in the edge-case checklist |
| A11y | `axe-core` per story (planned, not built) | Blocking in CI |
| Visual | Playwright screenshots (planned, not built) | Every component × {light, dark} × {default, compact} × {320, 768, 1280} |
| Motion | Playwright traces (planned, not built) | Assert compositor-only: no layout/paint entries during an animation |
| Tokens | `scripts/check-tokens.ts` (planned, not built) | Contrast ratios computed from tokens; grid conformance on every spacing value |
| Registry | `scripts/check-registry.ts` (planned, not built) | Every item installs into a clean app and typechecks |

**The money math should get property-based tests.** `formatMoney(parseMoney(x)) === x` for all valid inputs, and `allocate(total, weights).sum() === total` for all inputs. Every component depends on those two holding, and a handful of hand-picked examples won't find the amount that breaks them.

---

## Commands

```bash
bun run gen              # catalog, docs index, glossary, registry (/r/*.json), tool registry
bun run dev              # gen, then the docs site on :3210
bun run build            # gen, then next build
bun run test             # vitest
bun run lint             # biome lint
bun run typecheck        # tsc --noEmit
bun run extension        # build the Chrome extension into extension/dist
bun x ultracite fix      # lint + format
```

**Pre-push, once, at the end of a change:**
```bash
bun x ultracite fix && bun run typecheck && bun run build
```

---

## Contribution rules

1. **Spec before code.** The component's four-layer spec in `docs/catalog/` is reviewed and approved before implementation starts. A PR with code and no spec is closed.
2. **No new token without a decision record.** A new color, spacing value or duration needs a note in the relevant `foundations/` doc saying which existing token fell short and why.
3. **No `biome-ignore`.** Fix the code, or change `biome.jsonc` with a justification.
4. **No committed `console.log` or `debugger`.**
5. **No hardcoded credentials.** `process.env`, always.
6. **Every PR runs the [quality bar](https://lopifi.com/md/docs/craft/quality-bar).** The checklist is a PR template.
7. **A component is `stable` only after the next-day review.** Merging doesn't make it stable.
