Skip to content
Back to skills

003 Name Skill E7c0bf95

ASecurity

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

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 31, 2026
developmenttypescriptgobashreacttestinggitapifrontendbackend

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 003-name-skill_e7c0bf95 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 003 Name Skill E7c0bf95?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 003 Name Skill E7c0bf95
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-003-name-skill-e7c0bf95/badge)](https://www.skillsdirectory.com/skills/tools-only-003-name-skill-e7c0bf95)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: policyengine-app
description: Developing policyengine-app-v2 — the main React frontend for policyengine.org
---

# 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
```

## 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>

// Wrong
<Title order={2}>Your Saved Policies</Title>
```

Exceptions: proper nouns (PolicyEngine), acronyms (IRS), official names (Child Tax Credit).

## Deployment

- Automatic on push to `main` via Vercel
- Domain: `policyengine.org` (website), `app.policyengine.org` (calculator)
- Team: `policy-engine` scope

## Key files

| File | Purpose |
|------|---------|
| `app/src/WebsiteRouter.tsx` | Main routes |
| `app/src/pages/AppPage.tsx` | Renders embedded apps |
| `app/src/data/apps/apps.json` | Tool registry |
| `app/src/designTokens/` | Token imports |
| `app/src/components/charts/` | Chart components |
| `packages/design-system/` | Token source of truth |
| `app/.claude/skills/` | Local skills (design-tokens, chart-standards, ingredient-patterns) |

## URL patterns

### Production domains

| Domain | Purpose |
|--------|---------|
| `policyengine.org` | Marketing website, research, blog |
| `app.policyengine.org` | Calculator app (policies, households, reports) |

### Calculator URLs (app.policyengine.org)

```
app.policyengine.org/:countryId/                             # Dashboard
app.policyengine.org/:countryId/policies                     # Saved policies
app.policyengine.org/:countryId/policies/create               # Policy builder
app.policyengine.org/:countryId/households                    # Saved households
app.policyengine.org/:countryId/households/create             # Household builder
app.policyengine.org/:countryId/reports                       # Saved reports
app.policyengine.org/:countryId/reports/create                # Report builder
app.policyengine.org/:countryId/simulations                   # Saved simulations
app.policyengine.org/:countryId/simulations/create            # Simulation builder
app.policyengine.org/:countryId/report-output/:reportId       # Report output (overview)
app.policyengine.org/:countryId/report-output/:reportId/:subpage/:view  # Specific chart
```

**Report output subpages and views:**
```
/report-output/:reportId/budget                               # Budget overview
/report-output/:reportId/distributional/incomeDecile          # Distributional by income
/report-output/:reportId/distributional/wealthDecile          # Distributional by wealth
/report-output/:reportId/winners-losers/incomeDecile          # Winners/losers by income
/report-output/:reportId/winners-losers/wealthDecile          # Winners/losers by wealth
/report-output/:reportId/poverty/age                          # Poverty by age
/report-output/:reportId/poverty/gender                       # Poverty by gender
/report-output/:reportId/poverty/race                         # Poverty by race (US only)
/report-output/:reportId/deep-poverty/age                     # Deep poverty by age
/report-output/:reportId/deep-poverty/gender                  # Deep poverty by gender
/report-output/:reportId/inequality                           # Inequality measures
```

**Country IDs:** `us`, `uk`, `ca`, `ng`, `il`

### Website URLs (policyengine.org)

```
policyengine.org/:countryId/                     # Country home
policyengine.org/:countryId/research             # Research index
policyengine.org/:countryId/research/:slug       # Research article
policyengine.org/:countryId/blog                 # Blog index
policyengine.org/:countryId/blog/:postName       # Blog post
policyengine.org/:countryId/model                # Policy model explorer
policyengine.org/:countryId/:slug                # Embedded app (from apps.json)
```

### Legacy v1 URLs (DO NOT USE)

The old `policyengine-app` (v1) used a different URL pattern that no longer works:
```
# WRONG — v1 format, returns "App not found"
policyengine.org/us/policy?reform=73278&baseline=2&region=enhanced_us&timePeriod=2025
policyengine.org/us/reform/2/280039/over/2/us?focus=policyOutput.winnersAndLosers.incomeDecile
```

Always use `app.policyengine.org` for calculator functionality.

## Related skills

- `policyengine-design-skill` — Full token reference
- `policyengine-interactive-tools-skill` — Building standalone tools
- `policyengine-vercel-deployment-skill` — Deployment patterns
- `policyengine-writing-skill` — Content style
- `policyengine-api-skill` — Backend API

Files in this skill

  • README.md950 B
  • skill.md10.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.