From ef7e8823081cfcf787d88c660530286ae0df2f10 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 22 Jan 2026 14:11:39 +0100 Subject: [PATCH 1/7] feat: add react-clean-patterns-1 rule for favoring composition over configuration --- .claude/agents/code-inline-reviewer.md | 125 +++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md index 7960d895ca39..41f774cd5053 100644 --- a/.claude/agents/code-inline-reviewer.md +++ b/.claude/agents/code-inline-reviewer.md @@ -825,6 +825,131 @@ async function submitForm(data: FormData) { --- +### [CLEAN-REACT-PATTERNS-1] Favor composition over configuration + +- **Search patterns**: `shouldShow`, `shouldEnable`, `canSelect`, `enable`, `disable`, configuration props patterns + +- **Condition**: Flag ONLY when ALL of these are true: + + - A **new component** is being introduced + - The component is implemented by adding configuration (props, flags, conditional logic) to existing components + - These configuration options control feature presence or behavior within the component + - The features could instead be expressed as child components + - The component's API is expanding with configuration options rather than staying stable + + **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 + +- **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 "god components." + +Good (composition): + +```tsx +// Features expressed as composable children +// Parent stays stable; add features by adding children + + + + +
+ + + + + +``` + +Bad (configuration): + +```tsx +// Features controlled by boolean flags +// Adding a new feature requires modifying the component's API + + +type SelectionListProps = { + shouldShowTextInput?: boolean; // ❌ Could be + shouldShowConfirmButton?: boolean; // ❌ Could be + textInputOptions?: {...}; // ❌ Configuration object for the above +}; +``` + +Bad (vertical growth): + +```tsx +// Structure that grows vertically over time +// Parent has to handle more state to make child components happy with properties +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 ( + <> + + // other features + + + ); +} +``` + +Good (horizontal growth): + +```tsx +// Structure that expands horizontally +// Tree grows with nested structures that keep concerns separated +// Adding new subcomponents (features) does not require changing the parent +function ReportScreen({ params: { reportID }}) { + return ( + <> + + // other features + + + ); +} + +// 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); + // ... +} +``` + +--- + ## Instructions 1. **First, get the list of changed files and their diffs:** From 8b79f9973a7a75f9fd532a6b0379c357494859ef Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 23 Jan 2026 12:14:08 +0100 Subject: [PATCH 2/7] fix god->mega components and a typo --- .claude/agents/code-inline-reviewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md index 41f774cd5053..6ce1e3792260 100644 --- a/.claude/agents/code-inline-reviewer.md +++ b/.claude/agents/code-inline-reviewer.md @@ -847,7 +847,7 @@ async function submitForm(data: FormData) { - The component uses composition and child components for features - Parent components stay stable as features are added -- **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 "god components." +- **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". Good (composition): From 030854c927f0c53e8c8dd6073faaf4de42a6b589 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 23 Jan 2026 13:22:33 +0100 Subject: [PATCH 3/7] clarify CLEAN-REACT-PATTERNS-1 condition for new features vs components --- .claude/agents/code-inline-reviewer.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md index 6ce1e3792260..224a83d148c3 100644 --- a/.claude/agents/code-inline-reviewer.md +++ b/.claude/agents/code-inline-reviewer.md @@ -831,11 +831,10 @@ async function submitForm(data: FormData) { - **Condition**: Flag ONLY when ALL of these are true: - - A **new component** is being introduced - - The component is implemented by adding configuration (props, flags, conditional logic) to existing components + - 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 - - The features could instead be expressed as child components - - The component's API is expanding with configuration options rather than staying stable + - 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 From 6ae8ac7e704baf91546f0a651613f3a0d8ffa3f2 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 23 Jan 2026 13:27:41 +0100 Subject: [PATCH 4/7] add cross-reference to AI reviewer rules in React Coding Standards section --- contributingGuides/STYLE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index 80b4725c06f4..e71e052c8dcd 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -71,6 +71,8 @@ We use Prettier to automatically style our code. - You can run Prettier to fix the style on all files with `npm run prettier` - You can run Prettier in watch mode to fix the styles when they are saved with `npm run prettier-watch` +Our codebase is also protected by automated code review rules that check for common patterns and anti-patterns. You can find the complete list of rules in the [Code Inline Reviewer documentation](../.claude/agents/code-inline-reviewer.md). + There are a few things that we have customized for our tastes which will take precedence over Airbnb's guide. ## TypeScript guidelines @@ -1016,6 +1018,8 @@ So, if a new language feature isn't something we have agreed to support it's off ## React Coding Standards +For additional React best practices and patterns that are automatically enforced during code review, see the [AI Code Reviewer Rules](../.claude/agents/code-inline-reviewer.md). + ### Code Documentation * Add descriptions to all component props using a block comment above the definition. No need to document the types, but add some context for each property so that other developers understand the intended use. From 0dc257e587171a7e88f0247ecac2c73dabe19776 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 23 Jan 2026 13:40:59 +0100 Subject: [PATCH 5/7] add Table component counter-example to CLEAN-REACT-PATTERNS-1 --- .claude/agents/code-inline-reviewer.md | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md index 224a83d148c3..492ce8817da3 100644 --- a/.claude/agents/code-inline-reviewer.md +++ b/.claude/agents/code-inline-reviewer.md @@ -850,15 +850,18 @@ async function submitForm(data: FormData) { Good (composition): +- Features expressed as composable children +- Parent stays stable; add features by adding children + ```tsx -// Features expressed as composable children -// Parent stays stable; add features by adding children
+``` +```tsx @@ -867,9 +870,32 @@ Good (composition): Bad (configuration): +- Features controlled by boolean flags +- Adding a new feature requires modifying the Table component's API + +```tsx + + +type TableProps = { + data: Item[]; + columns: Column[]; + shouldShowSearchBar?: boolean; // ❌ Could be + shouldShowHeader?: boolean; // ❌ Could be + shouldEnableSorting?: boolean; // ❌ Configuration for header behavior + shouldShowPagination?: boolean; // ❌ Could be + shouldHighlightOnHover?: boolean; // ❌ Configuration for styling behavior +}; +``` + ```tsx -// Features controlled by boolean flags -// Adding a new feature requires modifying the component's API Date: Fri, 23 Jan 2026 13:42:44 +0100 Subject: [PATCH 6/7] rm a redundant duplicated line --- contributingGuides/STYLE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index e71e052c8dcd..b61b678d0091 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -71,8 +71,6 @@ We use Prettier to automatically style our code. - You can run Prettier to fix the style on all files with `npm run prettier` - You can run Prettier in watch mode to fix the styles when they are saved with `npm run prettier-watch` -Our codebase is also protected by automated code review rules that check for common patterns and anti-patterns. You can find the complete list of rules in the [Code Inline Reviewer documentation](../.claude/agents/code-inline-reviewer.md). - There are a few things that we have customized for our tastes which will take precedence over Airbnb's guide. ## TypeScript guidelines From c33c11951234ca2c593920bdff8912f42cbb9c3a Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 26 Jan 2026 14:01:43 -0800 Subject: [PATCH 7/7] fix wording around horizontal/vertical growth over time --- .claude/agents/code-inline-reviewer.md | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md index 492ce8817da3..cbc07bf2fd5d 100644 --- a/.claude/agents/code-inline-reviewer.md +++ b/.claude/agents/code-inline-reviewer.md @@ -914,11 +914,36 @@ type SelectionListProps = { }; ``` -Bad (vertical growth): +Good (children manage their own state): ```tsx -// Structure that grows vertically over time -// Parent has to handle more state to make child components happy with properties +// 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 ( + <> + + // other features + + + ); +} + +// 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); + // ... +} +``` + +Bad (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]); @@ -948,31 +973,6 @@ function ReportScreen({ params: { reportID }}) { } ``` -Good (horizontal growth): - -```tsx -// Structure that expands horizontally -// Tree grows with nested structures that keep concerns separated -// Adding new subcomponents (features) does not require changing the parent -function ReportScreen({ params: { reportID }}) { - return ( - <> - - // other features - - - ); -} - -// 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); - // ... -} -``` - --- ## Instructions