Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,751 changes: 64 additions & 1,687 deletions .claude/agents/code-inline-reviewer.md

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions .claude/skills/coding-standards/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: coding-standards
description: Expensify coding standards for React Native — performance patterns, consistency rules, and clean React architecture. Guides both development and automated review.
alwaysApply: true
---

# Expensify Coding Standards

Coding standards for the Expensify App. Each standard is a standalone file in `rules/` with reasoning, examples, and applicability conditions.

## Categories

| Category | Prefix | Focus |
|----------|--------|-------|
| Performance | `PERF-*` | Render optimization, memo patterns, useEffect hygiene, data selection |
| Consistency | `CONSISTENCY-*` | Platform checks, magic values, unused props, ESLint discipline |
| Clean React Patterns | `CLEAN-REACT-PATTERNS-*` | Composition, component ownership, state structure |

## Quick Reference

### Performance
- [PERF-1](rules/perf-1-no-spread-in-renderitem.md) — No spread in renderItem
- [PERF-2](rules/perf-2-early-return.md) — Return early before expensive work
- [PERF-3](rules/perf-3-onyx-list-item-provider.md) — Use OnyxListItemProvider in renderItem
- [PERF-5](rules/perf-5-shallow-comparison.md) — Shallow over deep comparisons
- [PERF-6](rules/perf-6-derive-state-from-props.md) — Derive state from props
- [PERF-7](rules/perf-7-reset-via-key-prop.md) — Reset via key prop
- [PERF-8](rules/perf-8-events-in-handlers.md) — Handle events in handlers
- [PERF-9](rules/perf-9-no-useeffect-chains.md) — No useEffect chains
- [PERF-10](rules/perf-10-no-useeffect-parent-comm.md) — No useEffect parent communication
- [PERF-11](rules/perf-11-optimize-data-selection.md) — Optimize data selection
- [PERF-12](rules/perf-12-prevent-memory-leaks.md) — Prevent memory leaks
- [PERF-13](rules/perf-13-hoist-iterator-calls.md) — Hoist iterator-independent calls
- [PERF-14](rules/perf-14-use-sync-external-store.md) — Use useSyncExternalStore
- [PERF-15](rules/perf-15-cleanup-async-effects.md) — Clean up async Effects
- [PERF-16](rules/perf-16-guard-double-init.md) — Guard double initialization

### Consistency
- [CONSISTENCY-1](rules/consistency-1-no-platform-checks.md) — No platform-specific checks in components
- [CONSISTENCY-2](rules/consistency-2-no-magic-values.md) — No magic numbers/strings
- [CONSISTENCY-3](rules/consistency-3-no-code-duplication.md) — No code duplication
- [CONSISTENCY-4](rules/consistency-4-no-unused-props.md) — No unused props
- [CONSISTENCY-5](rules/consistency-5-justify-eslint-disable.md) — Justify ESLint disables
- [CONSISTENCY-6](rules/consistency-6-proper-error-handling.md) — Proper error handling

### Clean React Patterns
- [CLEAN-REACT-PATTERNS-1](rules/clean-react-1-composition-over-config.md) — Composition over configuration
- [CLEAN-REACT-PATTERNS-2](rules/clean-react-2-own-behavior.md) — Components own their behavior
- [CLEAN-REACT-PATTERNS-3](rules/clean-react-3-context-free-contracts.md) — Context-free component contracts
- [CLEAN-REACT-PATTERNS-4](rules/clean-react-4-no-side-effect-spaghetti.md) — No side-effect spaghetti
- [CLEAN-REACT-PATTERNS-5](rules/clean-react-5-narrow-state.md) — Keep state narrow

## Usage

**During development**: When writing or modifying `src/` files, consult the relevant standard files for detailed conditions, examples, and exceptions.

**During review**: The code-inline-reviewer agent loads all standards from this directory. See `.claude/agents/code-inline-reviewer.md`.
39 changes: 39 additions & 0 deletions .claude/skills/coding-standards/rules/_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
ruleId: CATEGORY-N
title: Human readable title
searchPatterns:
- "grepPattern1"
- "grepPattern2"
---

## [CATEGORY-N] Title

### Reasoning

Technical explanation of why the rule is important.

### Incorrect

```tsx
// Bad example — shows the problem
```

### Correct

```tsx
// Good example — shows the fix
```

---

### Review Metadata

**Condition**: Flag ONLY when ALL of these are true:

- Condition 1
- Condition 2

**DO NOT flag if:**

- Exception 1
- Exception 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
ruleId: CLEAN-REACT-PATTERNS-1
title: Favor composition over configuration
searchPatterns:
- "shouldShow"
- "shouldEnable"
- "canSelect"
- "enable"
- "disable"
---

## [CLEAN-REACT-PATTERNS-1] Favor composition over configuration

### Reasoning

When new features are implemented by adding configuration (props, flags, conditional logic) to existing components, if requirements change, then those components must be repeatedly modified, increasing coupling, surface area, and regression risk. Composition ensures features scale horizontally, limits the scope of changes, and prevents components from becoming configuration-driven "mega components".

### Incorrect

#### Incorrect (configuration)

- Features controlled by boolean flags
- Adding a new feature requires modifying the Table component's API

```tsx
<Table
data={items}
columns={columns}
shouldShowSearchBar
shouldShowHeader
shouldEnableSorting
shouldShowPagination
shouldHighlightOnHover
/>

type TableProps = {
data: Item[];
columns: Column[];
shouldShowSearchBar?: boolean; // Could be <Table.SearchBar />
shouldShowHeader?: boolean; // Could be <Table.Header />
shouldEnableSorting?: boolean; // Configuration for header behavior
shouldShowPagination?: boolean; // Could be <Table.Pagination />
shouldHighlightOnHover?: boolean; // Configuration for styling behavior
};
```

```tsx
<SelectionList
data={items}
shouldShowTextInput
shouldShowTooltips
shouldScrollToFocusedIndex
shouldDebounceScrolling
shouldUpdateFocusedIndex
canSelectMultiple
disableKeyboardShortcuts
/>

type SelectionListProps = {
shouldShowTextInput?: boolean; // Could be <SelectionList.TextInput />
shouldShowConfirmButton?: boolean; // Could be <SelectionList.ConfirmButton />
textInputOptions?: {...}; // Configuration object for the above
};
```

#### Incorrect (parent manages child state)

```tsx
// Parent fetches and manages state for its children
// Parent has to know child implementation details
function ReportScreen({ params: { reportID }}) {
const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {allowStaleData: true, canBeMissing: true});
const reportActions = useMemo(() => getFilteredReportActionsForReportView(unfilteredReportActions), [unfilteredReportActions]);
const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {canBeMissing: true, allowStaleData: true});
const {reportActions: unfilteredReportActions, linkedAction, sortedAllReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID, reportActionIDFromRoute);
const parentReportAction = useParentReportAction(reportOnyx);
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs);
const isTransactionThreadView = isReportTransactionThread(report);
// other onyx connections etc

return (
<>
<ReportActionsView
report={report}
reportActions={reportActions}
isLoadingInitialReportActions={reportMetadata?.isLoadingInitialReportActions}
hasNewerActions={hasNewerActions}
hasOlderActions={hasOlderActions}
parentReportAction={parentReportAction}
transactionThreadReportID={transactionThreadReportID}
isReportTransactionThread={isTransactionThreadView}
/>
// other features
<Composer />
</>
);
}
```

### Correct

#### Correct (composition)

- Features expressed as composable children
- Parent stays stable; add features by adding children

```tsx
<Table data={items} columns={columns}>
<Table.SearchBar />
<Table.Header />
<Table.Body />
</Table>
```

```tsx
<SelectionList data={items}>
<SelectionList.TextInput />
<SelectionList.Body />
</SelectionList>
```

#### Correct (children manage their own state)

```tsx
// Children are self-contained and manage their own state
// Parent only passes minimal data (IDs)
// Adding new features doesn't require changing the parent
function ReportScreen({ params: { reportID }}) {
return (
<>
<ReportActionsView reportID={reportID} />
// other features
<Composer />
</>
);
}

// Component accesses stores and calculates its own state
// Parent doesn't know the internals
function ReportActionsView({ reportID }) {
const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const reportActions = getFilteredReportActionsForReportView(unfilteredReportActions);
// ...
}
```

---

### Review Metadata

#### Condition

Flag ONLY when ALL of these are true:

- A **new feature** is being introduced OR an **existing component's API is being expanded with new props**
- The change adds configuration properties (flags, conditional logic)
- These configuration options control feature presence or behavior within the component
- These features could instead be expressed as composable child components

**Features that should be expressed as child components:**
- Optional UI elements that could be composed in
- New behavior that could be introduced as new children
- Features that currently require parent component code changes

**DO NOT flag if:**
- Props are narrow, stable values needed for coordination between composed parts (e.g., `reportID`, `data`, `columns`)
- The component uses composition and child components for features
- Parent components stay stable as features are added
124 changes: 124 additions & 0 deletions .claude/skills/coding-standards/rules/clean-react-2-own-behavior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
ruleId: CLEAN-REACT-PATTERNS-2
title: Let components own their behavior and effects
searchPatterns:
- "Report"
- "Policy"
- "Transaction"
- "Actions"
- "useOnyx"
---

## [CLEAN-REACT-PATTERNS-2] Let components own their behavior and effects

### Reasoning

When parent components compute and pass behavioral state to children, if a child's requirements change, then parent components must change as well, increasing coupling and causing behavior to leak across concerns. Letting components own their behavior keeps logic local, allows independent evolution, and follows the principle: "If removing a child breaks parent behavior, coupling exists."

**Distinction from CLEAN-REACT-PATTERNS-3**: This rule is about data flow DOWN (parent -> child) — "Don't pass data the child can get itself."

### Incorrect

#### Incorrect (parent micromanages child's state)

- Parent gathers, computes, and dictates the child's entire contextual awareness
- Parent imports hooks/stores only because the child needs the information
- Double coupling: parent -> child's dependencies, child -> prop names/types

```tsx
<OptionRowLHNData
reportID={reportID}
fullReport={item}
reportAttributes={itemReportAttributes}
oneTransactionThreadReport={itemOneTransactionThreadReport}
reportNameValuePairs={itemReportNameValuePairs}
reportActions={itemReportActions}
parentReportAction={itemParentReportAction}
iouReportReportActions={itemIouReportReportActions}
policy={itemPolicy}
invoiceReceiverPolicy={itemInvoiceReceiverPolicy}
personalDetails={personalDetails ?? {}}
transaction={itemTransaction}
lastReportActionTransaction={lastReportActionTransaction}
receiptTransactions={transactions}
viewMode={optionMode}
isOptionFocused={!shouldDisableFocusOptions}
lastMessageTextFromReport={lastMessageTextFromReport}
onSelectRow={onSelectRow}
preferredLocale={preferredLocale}
hasDraftComment={hasDraftComment}
transactionViolations={transactionViolations}
onLayout={onLayoutItem}
shouldShowRBRorGBRTooltip={shouldShowRBRorGBRTooltip}
activePolicyID={activePolicyID}
onboardingPurpose={introSelected?.choice}
isFullscreenVisible={isFullscreenVisible}
isReportsSplitNavigatorLast={isReportsSplitNavigatorLast}
isScreenFocused={isScreenFocused}
localeCompare={localeCompare}
testID={index}
isReportArchived={isReportArchived}
lastAction={lastAction}
lastActionReport={lastActionReport}
/>
```

In this example:
- The parent fetches `fullReport`, `policy`, `transaction`, `reportActions`, `personalDetails`, `transactionViolations`, and routing/layout state
- These dependencies exist only because the child needs them — the parent is a data intermediary
- If `OptionRowLHNData` requirements change, the parent must change too

### Correct

#### Correct (component owns its behavior)

- Component receives only IDs and handlers
- Internally accesses stores, contexts, and computes values
- Children follow the same pattern

```tsx
<OptionRowLHNData
reportID={reportID}
onSelectRow={onSelectRow}
/>
```

```tsx
function OptionRowLHNData({reportID, onSelectRow}) {
// Component fetches its own data
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`);
const [viewMode] = useOnyx(ONYXKEYS.NVP_VIEW_MODE);
// ... other data this component needs

return (
<View>
{/* Children own their state too */}
<Avatars reportID={reportID} />
<DisplayNames reportID={reportID} />
<Status reportID={reportID} />
</View>
);
}
```

---

### Review Metadata

#### Condition

Flag when a parent component acts as a pure data intermediary — fetching or computing state only to pass it to children without using it for its own logic.

**Signs of violation:**
- Parent imports hooks/contexts only to satisfy child's data needs
- Props that are direct pass-throughs of hook results (e.g., `report={reportOnyx}`)
- Component receives props that are just passed through to children or that it could fetch itself
- Removing or commenting out the child would leave unused variables in the parent

**DO NOT flag if:**
- Props are minimal, domain-relevant identifiers (e.g., `reportID`, `transactionID`, `policyID`)
- Props are callback/event handlers for coordination (e.g., `onSelectRow`, `onLayout`, `onPress`)
- Props are structural/presentational that can't be derived internally (e.g., `style`, `testID`)
- Parent genuinely uses the data for its own rendering or logic
- Data is shared coordination state that parent legitimately owns (e.g., selection state managed by parent)
Loading
Loading