Claude
Skills
Sign in
Back

policyengine-app

Included with Lifetime
$97 forever

Developing policyengine-app-v2 — the main React frontend for policyengine.org

Web Dev

What this skill does


# PolicyEngine app (v2)

Architecture and patterns for developing the main PolicyEngine web application at `policyengine.org`.

**Repository:** `PolicyEngine/policyengine-app-v2`

## Architecture

### Monorepo

```
policyengine-app-v2/
├── packages/
│   └── design-system/          # @policyengine/design-system (npm)
├── app/                        # Main Vite application
│   ├── src/
│   │   ├── pages/              # Page components (*.page.tsx)
│   │   ├── components/         # Shared UI (charts, layouts, modals)
│   │   ├── routing/            # Guards, router config
│   │   ├── hooks/              # Custom React hooks
│   │   ├── designTokens/       # Re-exports from design-system
│   │   ├── styles/             # Mantine theme, global PostCSS
│   │   ├── data/               # Static data (apps.json, posts/)
│   │   ├── adapters/           # API fetch wrappers
│   │   ├── api/                # React Query hooks
│   │   ├── contexts/           # React Context providers
│   │   ├── types/              # TypeScript interfaces
│   │   └── utils/              # Formatters, helpers
│   ├── public/                 # Static assets (logos, post images)
│   └── vite.config.mjs
├── turbo.json
└── package.json                # Bun workspaces root
```

### Tech stack

| Layer | Technology |
|-------|-----------|
| Package manager | **Bun** (primary), npm fallback |
| Build | Vite + Turbo |
| UI framework | **Mantine v8** |
| Routing | React Router v7 (`createBrowserRouter`) |
| Charts | **Recharts** (standard), Plotly (maps only) |
| Server state | React Query |
| Design tokens | `@policyengine/design-system` |
| Language | TypeScript |
| Formatting | Prettier + ESLint |
| Testing | Vitest |

### Dual SPA mode

`VITE_APP_MODE` controls which entry point builds:
- `website` — Full policyengine.org (pages, blog, research, embedded tools)
- `calculator` — Standalone calculator at app.policyengine.org

## Development

```bash
bun install                          # Install dependencies
bun run dev                          # Dev server (builds design-system first)
cd app && bun run prettier -- --write .  # Format before committing
bun run lint                         # Lint (CI uses --max-warnings 0)
bun run build                        # Production build
bun run test                         # Tests
```

## CRITICAL: Frontend verification and troubleshooting

### How to verify the frontend works

The ONLY reliable way to verify the frontend compiles correctly is `bun run build`. This catches import errors, TypeScript errors, and missing dependencies.

**`curl` is NOT verification.** Vite serves an HTML shell regardless of whether React components actually render. A 200 from curl means nothing for an SPA. Never use curl output as evidence that the frontend works.

**You cannot visually verify a frontend.** After the build passes and dev server starts, tell the user it's ready for them to check in the browser. Do not claim it "looks good."

Before making any claim about dev server status, check with `lsof -i :5173`. Do not assume it is running.

### Correct verification sequence

```bash
bun run build                        # Catches all compile-time errors
# If build passes and dev server needed:
cd app && VITE_APP_MODE=website ./node_modules/.bin/vite --port 5173 &
open http://localhost:5173/us         # User checks visually
```

### Dependency troubleshooting

When `bun install` fails:

1. Read the error. Fix the specific issue.
2. Try at most **2 approaches** before asking the user for help.
3. **Hard stops — never do these:**
   - `rm -rf node_modules` without user approval
   - Manually `npm pack` + `tar -xzf` packages into node_modules
   - Editing lockfile internals
   - Looping through increasingly obscure npm/bun flags

## Design tokens

Import from `@/designTokens` (convenience layer that re-exports from the design-system package):

```tsx
import { colors, spacing, typography } from '@/designTokens';
```

**Never hardcode values:**
```tsx
// Wrong
style={{ color: '#319795', marginBottom: '16px' }}

// Correct
style={{ color: colors.primary[500], marginBottom: spacing.lg }}
```

See `policyengine-design-skill` for the full token reference.

## Mantine components

All UI uses Mantine v8. Key components:

```tsx
import { Stack, Group, Text, Button, Paper } from '@mantine/core';
import { colors, spacing } from '@/designTokens';

function PolicyCard({ title, description, onEdit }) {
  return (
    <Paper p={spacing.lg} withBorder>
      <Stack gap={spacing.sm}>
        <Text fw={600}>{title}</Text>
        <Text c={colors.text.secondary} fz="sm">{description}</Text>
        <Button variant="outline" onClick={onEdit}>Edit</Button>
      </Stack>
    </Paper>
  );
}
```

| Component | Usage |
|-----------|-------|
| `Stack`, `Group`, `Box` | Layout |
| `Text`, `Title` | Typography |
| `Button`, `ActionIcon` | Controls |
| `Paper`, `Card` | Containers |
| `Table`, `Modal`, `Tooltip` | Complex UI |
| `TextInput`, `Select`, `NumberInput` | Forms |

## Charts

### Recharts (standard for all new charts)

```tsx
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { ChartContainer, ChartWatermark, ImpactTooltip } from '@/components/charts';
import { colors } from '@/designTokens';

function RevenueChart({ data }) {
  return (
    <ChartContainer title="Revenue impact" csvData={data}>
      <ResponsiveContainer width="100%" height={400}>
        <BarChart data={data}>
          <XAxis dataKey="name" />
          <YAxis tickFormatter={(v) => v.toLocaleString('en-US', {
            style: 'currency', currency: 'USD', notation: 'compact', maximumFractionDigits: 1,
          })} />
          <Tooltip content={<ImpactTooltip />} />
          <Bar dataKey="value" fill={colors.primary[500]} />
        </BarChart>
      </ResponsiveContainer>
      <ChartWatermark />
    </ChartContainer>
  );
}
```

### Semantic chart colors

| Meaning | Token |
|---------|-------|
| Primary data | `colors.primary[500]` |
| Secondary | `colors.gray[400]` |
| Positive/gains | `colors.success` |
| Negative/losses | `colors.gray[600]` |
| Error | `colors.error` |

### Plotly (maps only)

Plotly is only used for geographic visualizations (choropleths, hex maps). All other charts use Recharts.

### Chart components

| Component | Purpose |
|-----------|---------|
| `ChartContainer` | Card wrapper with title, CSV download |
| `ChartWatermark` | PolicyEngine logo below chart |
| `ImpactTooltip` | Formatted hover tooltip |
| `ImpactBarLabel` | Values above/below bars |

## Routing

Routes in `app/src/WebsiteRouter.tsx`:

```
/:countryId/
  ├── (StaticLayout)
  │   ├── home (index)
  │   ├── research/:slug (blog posts)
  │   ├── brand/*, team, donate
  │   └── model
  ├── (AppLayout)
  │   └── :slug → AppPage.tsx (apps.json)
  └── (full-page embeds)
```

### Country guards

- `CountryGuardSimple` — Validates countryId, redirects to default
- `CountryAppGuard` — Validates slug+countryId for apps

### Adding a new page

1. Create `app/src/pages/MyPage.page.tsx`
2. Add route in `WebsiteRouter.tsx` under appropriate layout
3. Use `useParams()` for `countryId`

## Embedded apps (apps.json)

Interactive tools register in `app/src/data/apps/apps.json` and render via `AppPage.tsx`:

```json
{
  "type": "iframe",
  "slug": "marriage",
  "title": "Marriage calculator",
  "source": "https://marriage-zeta-beryl.vercel.app/",
  "countryId": "us",
  "displayWithResearch": true
}
```

See `policyengine-interactive-tools-skill` for the full embedding pattern.

## Ingredient CRUD pages

Policies, Reports, Simulations, and Populations follow a shared pattern using `IngredientReadView` and `RenameIngredientModal`. See `app/.claude/skills/ingredient-patterns.md` for details.

## Blog posts

Markdown files in `app/src/data/posts/articles/`. Metadata in `posts.json`.

## Sentence case

Strictly enforced everywhere:

```tsx
// Correct
<Title order={2}>Your saved policies</Title>

// 

Related in Web Dev