NEVER run gh pr merge until ALL CI checks show "pass" status.
Before merging ANY PR:
- Run
gh pr checks [PR_NUMBER] --repo PolicyEngine/policyengine-app - Verify EVERY check shows "pass" (not "pending" or "fail")
- Wait if any checks are still "pending"
- Only merge when you see:
CI pass,Lint pass,Vercel pass(the CI check name may include a Node version likeCI (22.x))
If you merge before CI completes, you risk breaking production.
- Install:
make install(npm ci & black) - Start dev server:
make debug(ornpm start) - Run all tests:
make testornpm run test - Run single test:
npx jest path/to/test.jsor with pattern:npx jest -t "test name pattern" - Lint & format:
make format(runs prettier & eslint fix, use this before committing) - Check lint only:
npm run lint - Coverage report: Check the
coverage/directory after running tests
- Node version: Use Node >=22.0.0 (use nvm to manage versions)
- Formatting: Uses Prettier (default config) with ESLint
- React:
- Functional components with hooks
- No need to import React in most files due to new JSX transform (React 17+)
- Only import React when using React namespace features directly (React.lazy, React.Suspense, etc.)
- Use named exports from React (e.g.,
import { useState, useEffect } from "react")
- Imports:
- Group by external/internal
- Prefer named imports
- Keep imports organized and minimize unused imports
- Testing: Jest with React Testing Library; tests in
__tests__directory with.test.jssuffix - File naming: Use PascalCase for components, camelCase for utilities
- TypeScript: Gradually adopting; new files should use TypeScript when possible
- Error handling: Use try/catch for API calls; display user-friendly error messages
- Pre-commit hooks: Uses husky and lint-staged to enforce linting on commit
- Component size: Keep functions less than 150 lines after formatting
- UI Components: Uses Ant Design (
antd) for UI components (Button, Switch, Dropdown, etc.) - State management: No global state - prefer props and lifting state up
- React Router: Uses
react-router-domv6 with search params for state preservation - Charts/Graphs: Uses
plotly.jsandreact-plotly.jsfor data visualization - Markdown: Uses
react-markdownwith plugins for rendering markdown content - Page Structure: Typically follows Header -> PageHeader -> Content -> Footer pattern
- Country-specific content: Use the
countryIdhook and conditionals/props to handle US/UK differences - Image loading: Use
require()for dynamic images rather than direct string paths
- Use the spread operator for state updates:
setState({...state, property: value}) - Use regex with global flag (
/pattern/g) when replacing multiple occurrences in strings findInTreefunction navigates variable hierarchies using path strings like "input.household.children"- URL-encoded parameters need proper decoding (e.g., %5B to [, %5D to ])
- React.useEffect with empty deps array (
[]) runs once on mount, no array runs on every render - Always include all dependencies in React hooks' dependency arrays (especially with useCallback and useEffect)
- For shared components used across multiple country pages (UK/US), create a common component and pass country info as props
- When creating new pages, ensure they follow the same page structure as existing ones (i.e., include Header/Footer)
- Follow the "PolicyEngine React commandments" in
src/README.mdfor component structure - Add docstrings to all components and graceful error handling
- When making changes, follow existing patterns in the codebase
- Run
npm run lint -- --max-warnings=0locally to ensure the CI pipeline will pass (CI uses zero tolerance for warnings)
- Color contrast: Ensure text remains readable on hover states - avoid blue text on blue backgrounds
- Prefer text underlines or other non-color indicators for hover states when possible
- Add appropriate ARIA attributes to interactive and multimedia elements:
- Use
aria-labelfor iframes, images, and controls that need better descriptions - Include
titleattribute for iframes
- Use
- Avoid using logical operators (
&&) for compound assignments in event handlers - use explicit code blocks with braces{} - Test all interactive elements with keyboard navigation (Tab key)
- When using ANTD components, check for accessibility-specific props and options
- Image elements should always have descriptive
alttext - Maintain focus visibility for keyboard users
- Test with a screen reader periodically to verify interface usability
- Keep URLs in blog post markdown files short (without query parameters) to prevent line-breaking issues
- When adding blog posts, remember to remove the title (with the single #), description, and cover image
- Use proper Markdown footnote syntax (
[^1]) instead of superscript footnotes - New blog posts in
posts.jsonshould be added at the beginning of the array (most recent first) - For new blog posts from Medium, install
mediumexporterand use it to download the post content - Image file paths should match the blog post filename in the posts.json entry
- Use factual, objective language throughout - avoid adjectives like "significantly," "slightly," or "progressively"
- Present data without qualitative judgments - let numbers speak for themselves
- Do not use title case for section headings or bullet points; use sentence case instead
- Always use sentence case for headings (capitalize only the first word and proper nouns)
- Avoid phrases like "small impact" or "large effect" - instead specify the exact percentage or numerical change
- Don't round numbers unless necessary, and when rounding, indicate the level of precision
- Ensure all chart titles could be quoted out of context and still provide full information
- Example: "Income tax threshold freeze to 2030: Change in net income by income decile"
- Include the specific reform name in the chart title
- When describing change, specify baseline and reform values when possible (e.g., "from 12.20% to 12.23%")
- Focus on "what" rather than "why" in descriptions of results
- Avoid words that imply value judgments (e.g., "burden," "benefit," "progressive," "regressive")
- When describing higher-numbered deciles, prefer "higher-income" over "rich" or "wealthy"
- When describing lower-numbered deciles, prefer "lower-income" over "poor"
- Use precise language: "would" rather than "could" or "might" when describing modeled outcomes
Based on our UK VAT thresholds article improvements, apply these standards to all PolicyEngine articles:
- Use active voice with clear subjects (e.g., "The UK requires..." not "Businesses are required...")
- Remove redundancy - Don't repeat information in multiple sentences
- Eliminate wordiness - "We estimate that" → just state the finding
- Avoid empty phrases - "It is important to note that" → just state the fact
- Combine related sentences to reduce repetition
- Only use adjectives/adverbs supported by data - avoid "substantial," "significant," "slightly" unless quantified
- Be precise about data sources - clearly attribute who constructed/analyzed what
- Remove obvious statements - e.g., "Higher thresholds reduce revenue" when a chart shows this
- Avoid hedging without reason - "approximately" only when rounding, not for exact model outputs
- Lead with findings - put key results upfront, methodology details later
- Remove duplicate information - don't present same data in both table and chart
- Keep introductions tight - 1-2 paragraphs maximum
- Make transitions implicit - avoid "In this section, we will..."
- Spell out acronyms on first use - "Retail Price Index (RPI)" then RPI thereafter
- Clarify baselines and assumptions - be explicit about what's held constant
- Describe distributions accurately - avoid "clustering" unless data shows clear bunching
- Use consistent terminology - pick one term and stick with it
- Keep conclusions factual - summarize findings without speculation
- Be specific about future work - only mention planned analyses, not aspirations
- Avoid promotional language - let the analysis speak for itself
- Make titles action-oriented - "How X affects Y" rather than "Modelling X"
- Include key finding in subtitle when possible
- Avoid redundant location markers for country-specific sites (don't say "UK" on UK site)