Skip to content

Commit 49b5976

Browse files
authored
Merge branch 'Expensify:main' into openPolicyCompanyCardsFeed-emailList
2 parents f96d225 + 31dc747 commit 49b5976

490 files changed

Lines changed: 17965 additions & 7324 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/agents/helpdot-inline-reviewer.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ You are **Support Doc Optimizer** — an AI trained to evaluate HelpDot articles
1111

1212
Your job is to scan through changed documentation files and create **inline comments** for specific violations based on the three core criteria below.
1313

14+
**CRITICAL — Review only the proposed changes:** You must evaluate and comment only on the **diff** (the lines added or modified in the PR). Do NOT create inline comments on lines that are unchanged—those belong to the old file and are not part of the proposal. Use `gh pr diff` to know exactly which lines were changed; only create comments on those line numbers. Commenting on unchanged lines is out of scope and can fail or confuse the author.
15+
1416
## 1. Readability Violations (Create inline comments for)
1517
- Poor sentence clarity, grammar, or scannability issues
1618
- Illogical flow or ordering of sections
@@ -20,6 +22,7 @@ Your job is to scan through changed documentation files and create **inline comm
2022

2123
## 2. AI Readiness Violations (Create inline comments for)
2224
- Vague headings without full feature names (e.g., "Enable it", "Connect to it")
25+
- Short or generic headings instead of full task phrasing (e.g., "Options" → "Expense Rule options for Workspace Admins"; use the full task and audience in the heading)
2326
- Non-descriptive headings (e.g., "Where to find it" vs "Where to find Statement Matching")
2427
- Vague references like "this," "that," or "it" without clear context
2528
- Missing or incomplete YAML metadata:
@@ -28,11 +31,14 @@ Your job is to scan through changed documentation files and create **inline comm
2831
title: [Full article title]
2932
description: [Concise, benefit-focused summary]
3033
keywords: [feature name, related terms, navigation path, etc.]
34+
internalScope: Audience is [target role]. Covers [main topic]. Does not cover [excluded areas].
3135
---
3236
```
33-
- Missing breadcrumb paths below H1 (Settings > Workspaces > People)
37+
- **internalScope** must always be present. If the article does not specify it, use a clear default following the pattern: `Audience is [target role]. Covers [main topic]. Does not cover [excluded areas].`
3438
- Wrong heading levels (using ### or deeper instead of # or ##)
3539

40+
**Note:** A breadcrumb path after the H1 heading is **not** required. Do not flag missing breadcrumbs as a violation.
41+
3642
## 3. Expensify Style Compliance Violations (Create inline comments for)
3743
- Voice and tone issues:
3844
- Not casual yet professional
@@ -52,11 +58,13 @@ keywords: [feature name, related terms, navigation path, etc.]
5258

5359
## Instructions
5460

55-
1. **First, get the list of changed files:**
56-
- Use `gh pr diff` to see what actually changed in the PR
57-
- Focus ONLY on documentation files (*.md, *.csv, etc.)
61+
1. **Get the diff and scope (required):**
62+
- Use `gh pr diff` to see the exact lines added or modified in the PR
63+
- Identify which file paths and line numbers are in the diff—these are the **only** lines you may comment on
64+
- Focus only on documentation files (*.md, *.csv, etc.)
5865

5966
2. **For analyzing changed files:**
67+
- **Restrict analysis to the diff:** When checking for violations, evaluate only content that appears on added or modified lines. If you read a full file for context, do not create inline comments on line numbers that are not part of the diff.
6068
- **Use a hybrid approach** because different violations require different analysis methods:
6169
- **Grep is suitable for pattern-based violations only:**
6270
- Terminology violations ("policy" → "workspace", "user" → "member")
@@ -75,7 +83,7 @@ keywords: [feature name, related terms, navigation path, etc.]
7583

7684
4. **Required parameters for each inline comment:**
7785
- `path`: Full file path (e.g., "docs/articles/new-expensify/chat/Create-a-New-Chat.md")
78-
- `line`: Line number where the issue occurs
86+
- `line`: Line number where the issue occurs**must be a line that appears in the PR diff (added or modified)**. Do not use line numbers from unchanged portions of the file.
7987
- `body`: Concise description of the violation and fix
8088

8189
## Tool Usage Example

.claude/agents/helpdot-summary-reviewer.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ You are a documentation quality specialist that provides comprehensive assessmen
1111

1212
Your job is to analyze all changed files and provide a single, comprehensive summary review with scores and overall recommendations.
1313

14+
**CRITICAL — Review only the proposed changes:** Base your assessment, scores, and recommendations **only on the changes being proposed** in the PR (the diff). Use `gh pr diff` to see what was added or modified. Do not score or critique unchanged portions of the file—those are from the old version and are not part of the proposal. Evaluate and feedback only on the diff.
15+
1416
## Scoring Criteria
1517

1618
### 1. Readability (1-10)
@@ -21,12 +23,13 @@ Your job is to analyze all changed files and provide a single, comprehensive sum
2123
- Proper use of formatting elements
2224

2325
### 2. AI Readiness (1-10)
24-
- Descriptive headings with full feature names
26+
- Descriptive headings with full feature names and full task phrasing (e.g., "Expense Rule options for Workspace Admins" not "Options")
2527
- Clear context without vague references
26-
- Proper YAML metadata structure
27-
- Breadcrumb navigation paths
28+
- Proper YAML metadata structure, including **internalScope** in the form: `Audience is [target role]. Covers [main topic]. Does not cover [excluded areas].` (use a clear default if not provided)
2829
- Consistent heading hierarchy (# and ## only)
2930

31+
**Note:** Breadcrumb paths after H1 are not required; do not penalize for their absence.
32+
3033
### 3. Style Compliance (1-10)
3134
- Expensify voice and tone standards
3235
- Correct terminology (workspace, member, etc.)
@@ -64,9 +67,10 @@ Provide your assessment as a **top-level PR comment** using this format:
6467

6568
## Instructions
6669

67-
1. **Analyze all changed documentation files**
68-
2. **Look for patterns and overall quality trends**
69-
3. **Provide balanced feedback** (both positive and areas for improvement)
70-
4. **Focus on the big picture** rather than individual line issues
71-
5. **Use Bash(gh pr comment:*) tool** to post the summary comment
72-
6. **Reference that inline comments provide specific details**
70+
1. **Get the diff first:** Use `gh pr diff` to see the exact proposed changes. Your entire assessment (scores, findings, recommendations) must be based only on added or modified lines—not on unchanged content from the old file.
71+
2. **Analyze only the proposed changes** in each documentation file
72+
3. **Look for patterns and overall quality trends** within the diff
73+
4. **Provide balanced feedback** (both positive and areas for improvement) — only for the proposed changes
74+
5. **Focus on the big picture** rather than individual line issues
75+
6. **Use Bash(gh pr comment:*) tool** to post the summary comment
76+
7. **Reference that inline comments provide specific details**

.claude/skills/coding-standards/rules/perf-11-optimize-data-selection.md

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,145 @@ title: Optimize data selection and handling
77

88
### Reasoning
99

10-
Using broad data structures or performing unnecessary data operations causes excessive re-renders and degrades performance. Selecting specific fields and avoiding redundant operations reduces render cycles and improves efficiency.
10+
`useOnyx` supports an optional `selector` to narrow data before it reaches the component. Selectors control both **what** data the component receives and **how** Onyx detects changes:
11+
12+
- **With a selector**: Onyx runs `deepEqual` on the selector output to decide whether to re-render. This guards against unnecessary re-renders when unrelated data changes, but the comparison cost scales with the size of the output.
13+
- **Without a selector**: Onyx uses `shallowEqual` on raw references, which is much cheaper. However, the component will re-render whenever **any** part of the subscribed data changes, since there is no narrowing to filter out irrelevant updates.
14+
15+
This means selectors are a double-edged sword. A well-written selector that returns a primitive or a small object is highly effective — it skips re-renders when unrelated data changes, and `deepEqual` on a small value is trivial. But a poorly-written selector that returns a large object, a full collection, or a non-plain type like `Set`/`Map` makes things worse — it forces an expensive `deepEqual` on every Onyx update with no re-render savings.
16+
17+
**When to use a selector:**
18+
- To pick a few fields from a single Onyx key (reduces re-renders from unrelated field changes)
19+
- To compute a final scalar (boolean, number, string) from a larger dataset
20+
21+
**When NOT to use a selector:**
22+
- To transform or reshape data without reducing its size — subscribe without a selector and transform inline instead
23+
- To extract a large sub-property (e.g., `(data) => data?.reports`) — just access it directly after the hook
24+
- To filter/map entire collections into arrays — the output is still large, `deepEqual` still expensive
25+
- To return `Set` or `Map``deepEqual` is extremely slow on these types
1126

1227
### Incorrect
1328

1429
```tsx
15-
function UserProfile({ userId }) {
16-
const [user] = useOnyx(`${ONYXKEYS.USER}${userId}`);
17-
// Component re-renders when any user field changes, even unused ones
18-
return <Text>{user?.name}</Text>;
30+
// BAD: No selector — component re-renders when any user field changes, even unused ones
31+
function UserProfile({userId}) {
32+
const [user] = useOnyx(`${ONYXKEYS.USER}${userId}`);
33+
return <Text>{user?.name}</Text>;
1934
}
35+
36+
// BAD: Selector maps an entire collection — deepEqual must compare every item
37+
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
38+
selector: (policies) =>
39+
Object.fromEntries(
40+
Object.entries(policies ?? {}).map(([key, policy]) => [
41+
key,
42+
{id: policy?.id, name: policy?.name, type: policy?.type},
43+
]),
44+
),
45+
});
46+
47+
// BAD: Selector extracts a large sub-property without narrowing
48+
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {
49+
selector: (data) => data?.reports,
50+
});
51+
52+
// BAD: Selector filters/maps a collection into an array — deepEqual on every item
53+
const [archivedReportIdsArray] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
54+
selector: (data): string[] =>
55+
Object.entries(data ?? {})
56+
.filter(([, value]) => value?.isArchived)
57+
.map(([key]) => key),
58+
});
59+
60+
// BAD: Selector returns a Set — deepEqual is extremely slow on Sets
61+
const [archivedReportIds] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
62+
selector: (data): Set<string> => {
63+
const ids = new Set<string>();
64+
Object.entries(data ?? {}).forEach(([key, value]) => {
65+
if (value?.isArchived) {
66+
ids.add(key);
67+
}
68+
});
69+
return ids;
70+
},
71+
});
72+
73+
// BAD: Selector returns an array when the component only needs a boolean
74+
const [reportSummaries] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {
75+
selector: (reports) =>
76+
Object.values(reports ?? {}).filter((r) => r?.total === 0),
77+
});
78+
const hasEmptyReports = reportSummaries.length > 0;
2079
```
2180

2281
### Correct
2382

2483
```tsx
25-
function UserProfile({ userId }) {
26-
const [user] = useOnyx(`${ONYXKEYS.USER}${userId}`, {
27-
selector: (user) => ({
28-
name: user?.name,
29-
avatar: user?.avatar,
30-
}),
31-
});
32-
return <Text>{user?.name}</Text>;
84+
// GOOD: Selector picks only the fields the component needs from a single item
85+
function UserProfile({userId}) {
86+
const [user] = useOnyx(`${ONYXKEYS.USER}${userId}`, {
87+
selector: (user) => ({
88+
name: user?.name,
89+
avatar: user?.avatar,
90+
}),
91+
});
92+
return <Text>{user?.name}</Text>;
3393
}
94+
95+
// GOOD: No selector on collection — shallowEqual is cheap, transform inline
96+
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
97+
const mappedPolicies = Object.fromEntries(
98+
Object.entries(policies ?? {}).map(([key, policy]) => [
99+
key,
100+
{id: policy?.id, name: policy?.name, type: policy?.type},
101+
]),
102+
);
103+
104+
// GOOD: No selector — access the property directly
105+
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES);
106+
const reports = reportAttributes?.reports;
107+
108+
// GOOD: No selector — filter and compute Set inline
109+
const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS);
110+
const archivedReportIds = new Set(
111+
Object.entries(reportNameValuePairs ?? {})
112+
.filter(([, value]) => value?.isArchived)
113+
.map(([key]) => key),
114+
);
115+
116+
// GOOD: Selector computes the final boolean directly
117+
const [hasEmptyReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {
118+
selector: (reports) =>
119+
Object.values(reports ?? {}).some((r) => r?.total === 0),
120+
});
34121
```
35122

36123
---
37124

38125
### Review Metadata
39126

40-
Flag ONLY when ALL of these are true:
127+
Flag ONLY when ANY of these are true:
41128

42-
- A component uses a broad data structure (e.g., entire object) without selecting specific fields
43-
- This causes unnecessary re-renders when unrelated fields change
44-
- OR unnecessary data filtering/fetching is performed (excluding necessary data, fetching already available data)
129+
- A component uses `useOnyx` and either:
130+
- Subscribes to a broad data structure without selecting specific fields, causing re-renders when unrelated fields change
131+
- Uses a `selector` whose output is still large or complex (e.g., full collection, large mapped/transformed result, `Set`, `Map`), or returns an intermediate data structure that is further reduced by the component
132+
- A selector on a collection references other large external datasets (e.g., another Onyx collection passed via closure) and iterates over them on every change to the subscribed collection, compounding the computation cost on unrelated updates
45133

46134
**DO NOT flag if:**
47135

48-
- Specific fields are already being selected or the data structure is static
49-
- The filtering is necessary for correct functionality
50-
- The fetched data is required and cannot be derived from existing data
51-
- The function requires the entire object for valid operations
136+
- The selector returns a primitive value (`boolean`, `string`, `number`, `undefined`)
137+
- The selector returns a small object with only a few fields picked from a single item (not a collection)
138+
- The selector meaningfully reduces a large dataset to a small result (e.g., a primitive or a few items) by iterating over the subscribed collection itself — the `deepEqual` cost on a small result is negligible
139+
- The `useOnyx` call is on a single-item key (not a collection), and the selector picks specific fields
140+
- The data structure is static or the function requires the entire object for valid operations
52141

53142
**Search Patterns** (hints for reviewers):
54143
- `useOnyx`
55144
- `selector`
56-
- `\.filter\(`
57-
- `\.map\(`
145+
- `useOnyx.*selector`
146+
- `selector.*=>`
147+
- `new Set\(`
148+
- `new Map\(`
149+
- `Object\.fromEntries`
150+
- `Object\.entries.*\.map`
151+
- `Object\.values.*\.filter`
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
ruleId: UI-1
3+
title: Use the correct loading indicator based on navigation context
4+
---
5+
6+
## [UI-1] Use the correct loading indicator based on navigation context
7+
8+
### Reasoning
9+
10+
If loading hangs, users need an escape route. When navigation (back button, close) is visible alongside the loader, users can escape - use `ActivityIndicator`. When no navigation is visible, users are trapped - use `FullscreenLoadingIndicator` with `shouldUseGoBackButton={true}` which shows an emergency "Go Back" button after timeout. This prop is being migrated to become default, so set it explicitly for now.
11+
12+
### Incorrect
13+
14+
```tsx
15+
// Header and FullscreenLoadingIndicator in SAME return - use ActivityIndicator
16+
<ScreenWrapper>
17+
<HeaderWithBackButton title="Settings" />
18+
<FullscreenLoadingIndicator />
19+
</ScreenWrapper>
20+
21+
// No navigation, missing shouldUseGoBackButton - user trapped if loading hangs
22+
function ValidateLoginPage() {
23+
return <FullscreenLoadingIndicator />;
24+
}
25+
26+
// ActivityIndicator as sole content without navigation - use FullscreenLoadingIndicator
27+
function AuthLoadingPage() {
28+
return (
29+
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter]}>
30+
<ActivityIndicator size="large" />
31+
</View>
32+
);
33+
}
34+
```
35+
36+
### Correct
37+
38+
```tsx
39+
// No navigation in return - FullscreenLoadingIndicator with shouldUseGoBackButton
40+
function ValidateLoginPage() {
41+
return <FullscreenLoadingIndicator shouldUseGoBackButton />;
42+
}
43+
44+
// Loader and navigation in DIFFERENT returns - OK to use FullscreenLoadingIndicator
45+
function SettingsPage() {
46+
if (isLoading) {
47+
return <FullscreenLoadingIndicator shouldUseGoBackButton />;
48+
}
49+
return (
50+
<ScreenWrapper>
51+
<HeaderWithBackButton title="Settings" />
52+
<Content />
53+
</ScreenWrapper>
54+
);
55+
}
56+
57+
// Header visible during loading - use ActivityIndicator
58+
function SettingsPage() {
59+
return (
60+
<ScreenWrapper>
61+
<HeaderWithBackButton title="Settings" />
62+
{isLoading ? (
63+
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter]}>
64+
<ActivityIndicator size="large" />
65+
</View>
66+
) : (
67+
<Content />
68+
)}
69+
</ScreenWrapper>
70+
);
71+
}
72+
```
73+
74+
---
75+
76+
### Review Metadata
77+
78+
Flag ONLY when ANY of these patterns is found:
79+
80+
- `FullscreenLoadingIndicator` and `HeaderWithBackButton` (or other navigation like close button) are **both under the same JSX tree** (not separated by conditionals)
81+
- `FullscreenLoadingIndicator` without `shouldUseGoBackButton` prop when **no navigation component** (e.g., `HeaderWithBackButton`, close button) **is rendered in the same return statement or conditional branch**
82+
- `ActivityIndicator` as the **sole/main screen content** (flex:1 container, early return) without any navigation component (e.g., `HeaderWithBackButton`, close button) **in the same return statement or conditional branch**
83+
84+
**DO NOT flag if:**
85+
86+
**For `FullscreenLoadingIndicator`:**
87+
- Visibility is controlled by `FullScreenLoaderContext`
88+
- Navigation visible in different conditional branches (separate return statement) AND has `shouldUseGoBackButton={true}`
89+
90+
**For `ActivityIndicator`:**
91+
- Used within interactive UI elements (buttons, list items, cards) where user can still interact with surrounding navigation
92+
93+
**Search Patterns** (hints for reviewers):
94+
- `FullscreenLoadingIndicator`
95+
- `FullScreenLoadingIndicator`
96+
- `ActivityIndicator`

.claude/skills/playwright-app-testing/SKILL.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ ps aux | grep "webpack" | grep -v grep
3030
## Playwright Testing Workflow
3131

3232
1. **Verify server**: Check webpack process is running
33-
2. **Navigate**: Use `mcp__playwright__browser_navigate` to `https://dev.new.expensify.com:8082/`
34-
3. **Interact**: Use Playwright MCP tools including:
35-
- **Inspection**: `browser_snapshot`, `browser_take_screenshot`, `browser_console_messages`
36-
- **Interaction**: `browser_click`, `browser_type`, `browser_fill_form`, `browser_hover`
37-
- **Navigation**: `browser_navigate_back`, `browser_tabs`, `browser_wait_for`
38-
- All other Playwright tools as needed
33+
2. **Navigate**: Open `https://dev.new.expensify.com:8082/` in the browser
34+
3. **Interact**: Use Playwright MCP tools to inspect, click, type, and navigate
35+
36+
Do NOT add arbitrary waits after actions. Instead, take a snapshot to check the result and only add short waits if the page hasn't updated yet.
3937

4038
## Dev Environment Sign-In
4139

0 commit comments

Comments
 (0)