diff --git a/.claude/agents/code-inline-reviewer.md b/.claude/agents/code-inline-reviewer.md
index c5c6d05f1bca..ebc9c8b2c974 100644
--- a/.claude/agents/code-inline-reviewer.md
+++ b/.claude/agents/code-inline-reviewer.md
@@ -12,6 +12,42 @@ You are a **React Native Expert** — an AI trained to evaluate code contributio
Your job is to scan through changed files and create **inline comments** for specific violations based on the below rules.
+## Mechanical Checking Process
+
+**CRITICAL**: You are a pattern-matching machine. Do not interpret, do not reason about intent. Follow these exact steps:
+
+### For EVERY file
+
+1. **Read the entire file first** with the Read tool
+2. **Create a TodoWrite checklist** with 6 items (one per rule) for this file
+3. **For each rule in order**, execute its mechanical check pattern (defined in each rule below)
+4. **Mark the todo complete** after checking each rule
+5. **Move to next file** only after all 6 rule checks are complete
+
+### What "mechanical checking" means
+
+- ❌ Do NOT ask "is this bad?" or "should this be memoized?"
+- ✅ DO ask "Does pattern X exist? If yes, check condition. True? → Flag."
+- ❌ Do NOT rationalize "but removing useMemo might be intentional"
+- ✅ DO check "Is variable X in a dependency array? Is it not memoized? → Flag."
+- ❌ Do NOT skip files because "they look fine"
+- ✅ DO search for each rule's patterns in every file, even if you expect zero matches
+
+### Your mental model
+
+You are executing this pseudocode:
+
+```javascript
+for each file in changed_files:
+ content = read(file)
+ for each rule in rules:
+ matches = search_patterns(content, rule.patterns)
+ for each match in matches:
+ if all_conditions_true(match, rule.conditions):
+ create_inline_comment(match, rule)
+ mark_todo_complete(file, rule)
+```
+
## Rules
Each rule includes:
@@ -220,25 +256,69 @@ const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
## Instructions
-1. **Read each changed file carefully** using the Read tool
-2. **For each violation found, immediately create an inline comment** using the available GitHub inline comment tool
-3. **Required parameters for each inline comment:**
+### Phase 1: Systematic Analysis (REQUIRED)
+
+**CRITICAL**: You MUST use the TodoWrite tool to create a systematic checklist before analyzing ANY code. This is not optional.
+
+1. **Create a todo list with TodoWrite containing one task per file to check against all rules**
+
+ Example: If there are 3 files, you should create 3 tasks.
+
+2. **Read each changed file completely** using the Read tool - never skip files or assume they're clean
+
+3. **For EACH file, systematically check against ALL rules**
+
+4. **Mark each task as completed** using TodoWrite as you finish checking each file against all rules
+
+5. **DO NOT proceed to Phase 2** until you have checked ALL files against ALL rules
+
+6. **If you believe something MIGHT be a Rule violation but are uncertain, err on the side of flagging it rather than skipping it.**
+
+7. **DO NOT invent new rules, stylistic preferences, or commentary outside the listed rules.**
+
+### Phase 2: Creating Comments
+
+1. **For each violation found, immediately create an inline comment** using the available GitHub inline comment tool
+
+2. **Required parameters for each inline comment:**
- `path`: Full file path (e.g., "src/components/ReportActionsList.tsx")
- `line`: Line number where the issue occurs
- - `body`: Concise and actionable description of the violation and fix, following the below Comment Format
-4. **Each comment must reference exactly one Rule ID.**
-5. **Output must consist exclusively of calls to mcp__github_inline_comment__create_inline_comment in the required format.** No other text, Markdown, or prose is allowed.
-6. **If no violations are found, output exactly** (with no quotes, markdown, or additional text):
+ - `body`: Concise and actionable description of the violation and fix, following the Comment Format below
+
+3. **Each comment must reference exactly one Rule ID.**
+
+4. **Output must consist exclusively of calls to mcp__github_inline_comment__create_inline_comment in the required format or a LGTM comment using gh pr comment tool** No other text, Markdown, or prose is allowed.
+
+5. **If no violations are found, create a comment using gh pr comment tool** (with no quotes, markdown, or additional text):
LGTM :feelsgood:. Thank you for your hard work!
-7. **Output LGTM if and only if**:
+
+6. **Output LGTM if and only if**:
+ - You created and completed ALL tasks in your TodoWrite checklist
- You examined EVERY line of EVERY changed file
- - You checked EVERY changed file against ALL rules
- - You found ZERO violations matching the exact rule criteria
+ - You checked EVERY changed file against ALL 6 rules systematically
+ - You found ZERO violations matching the rule criteria
- You verified no false negatives by checking each rule systematically
- If you found even ONE violation or have ANY uncertainty do NOT output LGTM - create inline comments instead.
-8. **DO NOT invent new rules, stylistic preferences, or commentary outside the listed rules.**
-9. **DO NOT describe what you are doing, output any summaries, explanations, extra content, comments on rules that are NOT violated or ANYTHING ELSE except from rules violations or LGTM message.**
- EXCEPTION: If you believe something MIGHT be a Rule violation but are uncertain, err on the side of creating an inline comment with your concern rather than skipping it.
+
+ If you found even ONE violation or have ANY uncertainty do NOT create LGTM comment - create inline comments instead.
+
+7. **DO NOT describe what you are doing, create comments with any summaries, explanations, extra content, comments on rules that are NOT violated or ANYTHING ELSE.**
+ Only inline comments regarding rules violations or general comments with LGTM message are allowed.
+
+### Patterns to follow
+
+✅ **DO** create a comprehensive TodoWrite checklist first
+✅ **DO** check every file against every rule methodically
+✅ **DO** use search patterns (grep mentally) for each rule's keywords
+✅ **DO** mark tasks complete as you go
+✅ **DO** if in doubt err on the side of flagging potential violations
+
+### Anti-Patterns to avoid
+
+❌ **DO NOT** scan files quickly and assume there are no violations
+❌ **DO NOT** skip creating the TodoWrite checklist
+❌ **DO NOT** check only some rules or some files
+❌ **DO NOT** rely on intuition - use systematic search patterns
+❌ **DO NOT** output LGTM without completing your entire checklist
## Tool Usage Example
@@ -251,6 +331,12 @@ mcp__github_inline_comment__create_inline_comment:
body: "
"
```
+If no violations are found, use the Bash tool to create a top-level PR comment:
+
+```bash
+gh pr comment --body "LGTM :feelsgood:. Thank you for your hard work!"
+```
+
## Comment Format
```
diff --git a/.claude/utils/test-review-prompt.sh b/.claude/utils/test-review-prompt.sh
new file mode 100755
index 000000000000..fc2f04e40c9c
--- /dev/null
+++ b/.claude/utils/test-review-prompt.sh
@@ -0,0 +1,243 @@
+#!/bin/bash
+# Local Claude Code Review Testing Script
+#
+# REQUIREMENTS:
+# - macOS
+# - Git repository
+# - Claude CLI installed (https://docs.claude.com/en/docs/claude-code)
+# - pbcopy (included with macOS)
+#
+# USAGE:
+# ./test-review.sh [prompt-file] [--manual]
+#
+# EXAMPLES:
+# ./test-review.sh main # Auto CLI mode, compare against main
+# ./test-review.sh develop --manual # Manual mode, compare against develop
+# ./test-review.sh main custom.md # Custom prompt file
+# ./test-review.sh main custom.md --manual # Custom prompt, manual mode
+#
+# DEFAULT BEHAVIOR: Automatically pipes to 'claude' command (CLI mode)
+# Use --manual flag to get instructions for manual copy/paste instead. Useful if you would like to paste it into a browser.
+
+set -e
+
+# Colors for output
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+# Configuration
+PROMPT_DIR="./prompts"
+REVIEW_OUTPUT_DIR="./review-output"
+DEFAULT_PROMPT="../agents/code-inline-reviewer.md"
+
+# Parse arguments
+TARGET_BRANCH=""
+PROMPT_FILE=""
+MANUAL_MODE=false
+
+for arg in "$@"; do
+ if [ "$arg" = "--manual" ]; then
+ MANUAL_MODE=true
+ elif [ -z "$TARGET_BRANCH" ]; then
+ TARGET_BRANCH="$arg"
+ elif [ -z "$PROMPT_FILE" ]; then
+ PROMPT_FILE="$arg"
+ fi
+done
+
+# Set defaults if not provided
+TARGET_BRANCH=${TARGET_BRANCH:-main}
+PROMPT_FILE=${PROMPT_FILE:-$DEFAULT_PROMPT}
+
+if [ "$MANUAL_MODE" = true ]; then
+ echo -e "${BLUE}=== Claude Code Review (Manual Mode) ===${NC}"
+else
+ echo -e "${BLUE}=== Claude Code Review (CLI Mode) ===${NC}"
+fi
+echo -e "${BLUE}Target branch: ${TARGET_BRANCH}${NC}"
+echo -e "${BLUE}Prompt file: ${PROMPT_FILE}${NC}\n"
+
+# Check if claude CLI is available (only in CLI mode)
+if [ "$MANUAL_MODE" = false ]; then
+ if ! command -v claude &> /dev/null; then
+ echo -e "${RED}Error: 'claude' command not found${NC}"
+ echo -e "${YELLOW}Please install Claude CLI: https://docs.claude.com/en/docs/claude-code${NC}"
+ echo -e "${YELLOW}Or use --manual flag for manual copy/paste mode${NC}"
+ exit 1
+ fi
+fi
+
+# Create necessary directories
+mkdir -p "$PROMPT_DIR"
+mkdir -p "$REVIEW_OUTPUT_DIR"
+
+# Check if prompt file exists
+if [ ! -f "$PROMPT_FILE" ]; then
+ echo -e "${RED}Error: Prompt file not found: ${PROMPT_FILE}${NC}"
+ echo -e "${YELLOW}Please ensure the prompt file exists at the specified path.${NC}"
+ exit 1
+fi
+
+# Get current branch
+CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
+echo -e "${BLUE}Current branch: ${CURRENT_BRANCH}${NC}\n"
+
+# Get the diff
+echo -e "${YELLOW}Generating diff against ${TARGET_BRANCH}...${NC}"
+TIMESTAMP=$(date +%Y%m%d-%H%M%S)
+DIFF_FILE="$REVIEW_OUTPUT_DIR/diff-${TIMESTAMP}.txt"
+git diff "$TARGET_BRANCH"...HEAD > "$DIFF_FILE"
+
+# Check if there are changes
+if [ ! -s "$DIFF_FILE" ]; then
+ echo -e "${RED}No changes found between ${CURRENT_BRANCH} and ${TARGET_BRANCH}${NC}"
+ rm "$DIFF_FILE"
+ exit 1
+fi
+
+echo -e "${GREEN}Diff saved to: ${DIFF_FILE}${NC}"
+echo -e "${BLUE}Diff stats:${NC}"
+git diff --stat "$TARGET_BRANCH"...HEAD
+echo ""
+
+# Get list of changed files
+echo -e "${YELLOW}Changed files:${NC}"
+git diff --name-only "$TARGET_BRANCH"...HEAD
+echo ""
+
+# Create review request file
+REVIEW_REQUEST="$REVIEW_OUTPUT_DIR/review-request-${TIMESTAMP}.txt"
+
+{
+ echo "Below you will see output for automated code review. For the purpose of this prompt: Instead of calling github actions output all comments to the console here, in the same format as if they were comments in GH. Otherwise follow the rules mentioned below in the PROMPT section."
+ echo ""
+ echo "IMPORTANT CONTEXT:"
+ echo "- All changed file contents are provided in full below"
+ echo "- You have complete access to review all code"
+ echo "- Do NOT attempt to use file reading tools or request permissions"
+ echo "- Analyze the provided diff and file contents directly"
+ echo "- The files are ready for your review - proceed with the analysis"
+ echo ""
+ echo "PROMPT"
+ cat "$PROMPT_FILE"
+ echo ""
+ echo "---"
+ echo ""
+ echo "Branch: $CURRENT_BRANCH -> $TARGET_BRANCH"
+ echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo ""
+ echo "Code Changes:"
+ echo ""
+ cat "$DIFF_FILE"
+ echo ""
+ echo "---"
+ echo ""
+ echo "## Full File Contents"
+ echo ""
+
+ # Add full content of changed files
+ git diff --name-only "$TARGET_BRANCH"...HEAD | while read -r file; do
+ if [ -f "$file" ]; then
+ echo "### File: $file"
+ echo '```'
+ cat "$file"
+ echo '```'
+ echo ""
+ fi
+ done
+} > "$REVIEW_REQUEST"
+
+echo -e "${GREEN}Review request prepared: ${REVIEW_REQUEST}${NC}\n"
+
+# Manual Mode: Provide instructions for copy/paste
+if [ "$MANUAL_MODE" = true ]; then
+ echo -e "${BLUE}╔═══════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${BLUE}║ ${YELLOW}Review request is ready!${BLUE} ║${NC}"
+ echo -e "${BLUE}╚═══════════════════════════════════════════════════════════╝${NC}"
+ echo ""
+ echo -e "${YELLOW}Option 1: Copy content to clipboard and paste to Claude CLI${NC}"
+ echo -e " cat \"${REVIEW_REQUEST}\" | pbcopy"
+ echo -e " claude"
+ echo -e " ${GREEN}# Then paste (Cmd+V) into Claude${NC}"
+ echo ""
+ echo -e "${YELLOW}Option 2: Use automatic CLI mode (recommended)${NC}"
+ echo -e " ./test-review.sh ${TARGET_BRANCH}${PROMPT_FILE:+ $PROMPT_FILE}"
+ echo ""
+ echo -e "${YELLOW}Option 3: View the file and manually copy${NC}"
+ echo -e " cat \"${REVIEW_REQUEST}\""
+ echo ""
+ echo -e "${YELLOW}Option 4: Open in your editor${NC}"
+ echo -e " \$EDITOR \"${REVIEW_REQUEST}\""
+ echo ""
+ echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}"
+ echo ""
+
+ # Ask if user wants to copy to clipboard
+ echo -e "${YELLOW}Would you like to copy to clipboard now? (y/n)${NC}"
+ read -r RESPONSE
+
+ if [[ "$RESPONSE" =~ ^[Yy]$ ]]; then
+ if command -v pbcopy &> /dev/null; then
+ pbcopy < "$REVIEW_REQUEST"
+ echo -e "${GREEN}✓ Copied to clipboard!${NC}"
+ echo -e "${YELLOW}Now run 'claude' and paste (Cmd+V)${NC}"
+ else
+ echo -e "${RED}Clipboard tool not found.${NC}"
+ echo -e "${YELLOW}Please manually copy from: ${REVIEW_REQUEST}${NC}"
+ fi
+ fi
+
+ echo ""
+ echo -e "${BLUE}After getting Claude's review, save it to:${NC}"
+ REVIEW_OUTPUT="$REVIEW_OUTPUT_DIR/review-result-${TIMESTAMP}.md"
+ echo -e "${GREEN}${REVIEW_OUTPUT}${NC}"
+ echo ""
+
+ # Summary
+ echo -e "${BLUE}=== Files Generated ===${NC}"
+ echo -e "Diff file: ${DIFF_FILE}"
+ echo -e "Review request: ${REVIEW_REQUEST}"
+ echo -e "Save review to: ${REVIEW_OUTPUT}"
+ echo -e "\n${GREEN}Preparation complete!${NC}"
+
+ exit 0
+fi
+
+# CLI Mode: Automatically copy to clipboard and pipe to claude
+echo -e "${BLUE}╔═══════════════════════════════════════════════════════════╗${NC}"
+echo -e "${BLUE}║ ${YELLOW}Automatically running Claude CLI${BLUE} ║${NC}"
+echo -e "${BLUE}╚═══════════════════════════════════════════════════════════╝${NC}"
+echo ""
+
+# Copy to clipboard
+if command -v pbcopy &> /dev/null; then
+ pbcopy < "$REVIEW_REQUEST"
+ echo -e "${GREEN}✓ Copied to clipboard${NC}"
+else
+ echo -e "${YELLOW}⚠ pbcopy not found, skipping clipboard copy${NC}"
+fi
+
+# Pipe to claude
+echo -e "${YELLOW}Running: claude -p < \"${REVIEW_REQUEST}\" ${NC}"
+echo ""
+echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}"
+echo ""
+
+claude -p < "$REVIEW_REQUEST"
+
+echo ""
+echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}"
+echo ""
+
+# Summary
+REVIEW_OUTPUT="$REVIEW_OUTPUT_DIR/review-result-${TIMESTAMP}.md"
+echo -e "${BLUE}=== Files Generated ===${NC}"
+echo -e "Diff file: ${DIFF_FILE}"
+echo -e "Review request: ${REVIEW_REQUEST}"
+echo -e "Save review to: ${REVIEW_OUTPUT}"
+echo ""
+echo -e "${GREEN}✓ Review complete!${NC}"
+echo -e "${YELLOW}Tip: Save the output above to ${REVIEW_OUTPUT}${NC}"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 83a61294711a..85cd1b71708c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -157,3 +157,7 @@ modules/*/lib/
# Claude local settings
.claude/settings.local.json
+
+# Claude prompt testing output
+.claude/utils/review-output
+
diff --git a/CLAUDE.md b/CLAUDE.md
index f3e86f53b3fe..0d74e3693b28 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -3,6 +3,7 @@
## Repository Overview
### Technology Stack
+
- **Framework**: React Native
- **Language**: TypeScript
- **State Management**: React Native Onyx
@@ -15,6 +16,7 @@
**IMPORTANT**: NewDot refers to the new Expensify App, OldDot or Expensify Classic refers to our Old expensify app and website
### Key Integration Points
+
- App (NewDot) and Mobile-Expensify (OldDot) are combined into a single mobile application
- The HybridApp module (`@expensify/react-native-hybrid-app`) manages transitions between OldDot and NewDot
- Build process merges dotenv configurations from both repositories
@@ -22,6 +24,7 @@
- Mobile builds **must** be initiated from the Mobile-Expensify directory
### Build Modes
+
- **Standalone**: Pure NewDot application (web/desktop)
- **HybridApp**: Combined OldDot + NewDot (mobile apps)
- Controlled via `STANDALONE_NEW_DOT` environment variable
@@ -29,13 +32,16 @@
## Core Architecture & Structure
### Entry Points
+
- `src/App.tsx`: Main application component with provider hierarchy
- `src/Expensify.tsx`: Core application logic and initialization
- `src/HybridAppHandler.tsx`: Manages HybridApp transitions and authentication
- `index.js`: React Native entry point
### Provider Architecture
+
The application uses a nested provider structure for context management:
+
1. **SplashScreenStateContextProvider**: Manages splash screen visibility
2. **InitialURLContextProvider**: Handles deep linking
3. **ThemeProvider**: Theme management
@@ -46,6 +52,7 @@ The application uses a nested provider structure for context management:
8. **KeyboardProvider**: Keyboard state management
### Data Layer
+
- **Onyx**: Custom data persistence layer for offline-first functionality
- **ONYXKEYS.ts**: Centralized key definitions for data store
- Supports optimistic updates and conflict resolution
@@ -53,6 +60,7 @@ The application uses a nested provider structure for context management:
## Key Features & Modules
### Core Functionality
+
1. **Expense Management**
- Receipt scanning and SmartScan
- Expense creation and editing
@@ -111,11 +119,13 @@ The application uses a nested provider structure for context management:
## Navigation & Routing
### Structure
+
- `src/SCREENS.ts`: Screen name constants
- `src/ROUTES.ts`: Route definitions and builders
- `src/NAVIGATORS.ts`: Navigator configuration
### Key Navigators
+
- **ProtectedScreens**: Authenticated app screens
- **PublicScreens**: Login and onboarding screens
- **RHP (Right Hand Panel/Pane)**: Settings and details panel
@@ -126,6 +136,7 @@ The application uses a nested provider structure for context management:
## State Management
### Onyx Keys Organization
+
- **Session**: Authentication and user session
- **Personal Details**: User profiles and preferences
- **Reports**: Chat and expense reports
@@ -134,7 +145,9 @@ The application uses a nested provider structure for context management:
- **Forms**: Form state management
### Action Modules (`src/libs/actions/`)
+
Major action categories:
+
- `App.ts`: Application lifecycle
- `IOU.ts`: Money requests and expenses
- `Report.ts`: Report management
@@ -147,7 +160,9 @@ Major action categories:
## Build & Deployment
### CI/CD Workflows
+
Key GitHub Actions workflows:
+
- `deploy.yml`: Production deployment
- `preDeploy.yml`: Staging deployment
- `testBuild.yml`: PR test builds
@@ -159,6 +174,7 @@ Key GitHub Actions workflows:
## Related Repositories
### Mobile-Expensify (Submodule)
+
- **Path**: `App/Mobile-Expensify/`
- **Purpose**: Legacy OldDot application and mobile build source
- **Critical**: All mobile builds originate from this directory
@@ -166,6 +182,7 @@ Key GitHub Actions workflows:
- Manages the HybridApp integration layer
### expensify-common
+
- **Purpose**: Shared libraries and utilities
- Contains common validation, parsing, and utility functions
- Used across multiple Expensify repositories
@@ -173,12 +190,14 @@ Key GitHub Actions workflows:
## Development Practices
### Code Quality
+
- **TypeScript**: Strict mode enabled
- **ESLint**: Linter
- **Prettier**: Automatic formatting
- **Patch Management**: patch-package for dependency fixes
### Testing
+
- **Unit Tests**: Jest with React Native Testing Library
- **E2E Tests**: Custom test runner
- **Performance Tests**: Reassure framework
@@ -186,31 +205,36 @@ Key GitHub Actions workflows:
## Special Considerations
### Offline-First Architecture
+
- All features work offline
- Optimistic updates with rollback
- Queue-based request handling
- Conflict resolution strategies
### Mobile-Specific Notes
+
- Push notifications via Airship
- Mapbox integration for location features
- Camera and gallery access
### Security
+
- Content Security Policy enforcement
- Two-factor authentication support
## Documentation Resources
### Help Documentation
-- **NewDot Help**: https://help.expensify.com/new-expensify/hubs/
-- **OldDot/Expensify Classic Help**: https://help.expensify.com/expensify-classic/hubs/
+
+- **NewDot Help**:
+- **OldDot/Expensify Classic Help**:
## Development Setup Requirements
## Command Reference
### Common Tasks
+
```bash
# Install dependencies
npm install
@@ -229,6 +253,7 @@ npm run test
```
### Platform Builds
+
```bash
# iOS build
npm run ios
@@ -246,17 +271,20 @@ npm run web
## Architecture Decisions
### React Native New Architecture
+
- Fabric renderer enabled
- TurboModules for native module integration
- Hermes JavaScript engine
### State Management Choice
+
- Custom Onyx library for offline-first capabilities
- Optimistic updates as default pattern
- Centralized action layer for API calls
- Direct key-value storage with automatic persistence
### Navigation Strategy
+
- React Navigation for cross-platform consistency
- Custom navigation state management
- Deep linking support
@@ -264,12 +292,14 @@ npm run web
## Known Integration Points
### With Mobile-Expensify
+
- Session sharing via HybridApp module
- Navigation handoff between apps
- Shared authentication state
- Environment variable merging
### With Backend Services
+
- RESTful API communication
- WebSocket connections via Pusher
- Real-time synchronization
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
index b24dd3c4d17c..eacfb3da435e 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
@@ -192,14 +192,12 @@ function MoneyRequestReportTransactionList({
const {sortBy, sortOrder} = sortConfig;
- const sortedTransactions: TransactionWithOptionalHighlight[] = useMemo(() => {
- return [...transactions]
- .sort((a, b) => compareValues(getTransactionValue(a, sortBy, report), getTransactionValue(b, sortBy, report), sortOrder, sortBy, localeCompare, true))
- .map((transaction) => ({
- ...transaction,
- shouldBeHighlighted: newTransactions?.includes(transaction),
- }));
- }, [newTransactions, sortBy, sortOrder, transactions, localeCompare, report]);
+ const sortedTransactions: TransactionWithOptionalHighlight[] = [...transactions]
+ .sort((a, b) => compareValues(getTransactionValue(a, sortBy, report), getTransactionValue(b, sortBy, report), sortOrder, sortBy, localeCompare, true))
+ .map((transaction) => ({
+ ...transaction,
+ shouldBeHighlighted: newTransactions?.includes(transaction),
+ }));
const columnsToShow = useMemo(() => {
const columns = getColumnsToShow(session?.accountID, transactions, true);
diff --git a/src/components/SubStepForms/YesNoStep.tsx b/src/components/SubStepForms/YesNoStep.tsx
index 4a9e08f7d67a..e58c446bedfb 100644
--- a/src/components/SubStepForms/YesNoStep.tsx
+++ b/src/components/SubStepForms/YesNoStep.tsx
@@ -37,19 +37,16 @@ function YesNoStep({title, description, defaultValue, onSelectedValue, submitBut
onSelectedValue(value);
};
const handleSelectValue = (newValue: string) => setValue(newValue === 'true');
- const options = useMemo(
- () => [
- {
- label: translate('common.yes'),
- value: 'true',
- },
- {
- label: translate('common.no'),
- value: 'false',
- },
- ],
- [translate],
- );
+ const options = [
+ {
+ label: translate('common.yes'),
+ value: 'true',
+ },
+ {
+ label: translate('common.no'),
+ value: 'false',
+ },
+ ];
return (
(sortedItems: TResource[], pages: Pages,
let lastItem = findLastItem(sortedItems, page, getID);
// If all actions in the page are not found it will be removed.
- if (firstItem === null || lastItem === null) {
- return null;
+ if (firstItem !== null && lastItem !== null) {
+ // In case actions were reordered, we need to swap them.
+ if (firstItem.index > lastItem.index) {
+ const temp = firstItem;
+ firstItem = lastItem;
+ lastItem = temp;
+ }
+
+ const ids = sortedItems.slice(firstItem.index, lastItem.index + 1).map((item) => getID(item));
+ if (firstItem.id === CONST.PAGINATION_START_ID) {
+ ids.unshift(CONST.PAGINATION_START_ID);
+ }
+ if (lastItem.id === CONST.PAGINATION_END_ID) {
+ ids.push(CONST.PAGINATION_END_ID);
+ }
+
+ return {
+ ids,
+ firstID: firstItem.id,
+ firstIndex: firstItem.index,
+ lastID: lastItem.id,
+ lastIndex: lastItem.index,
+ };
}
-
- // In case actions were reordered, we need to swap them.
- if (firstItem.index > lastItem.index) {
- const temp = firstItem;
- firstItem = lastItem;
- lastItem = temp;
- }
-
- const ids = sortedItems.slice(firstItem.index, lastItem.index + 1).map((item) => getID(item));
- if (firstItem.id === CONST.PAGINATION_START_ID) {
- ids.unshift(CONST.PAGINATION_START_ID);
- }
- if (lastItem.id === CONST.PAGINATION_END_ID) {
- ids.push(CONST.PAGINATION_END_ID);
- }
-
- return {
- ids,
- firstID: firstItem.id,
- firstIndex: firstItem.index,
- lastID: lastItem.id,
- lastIndex: lastItem.index,
- };
+ return null;
})
.filter((page): page is PageWithIndex => page !== null);
}
diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx
index ed16a598bd07..9614656f5ad3 100644
--- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx
+++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx
@@ -321,4 +321,4 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde
MoneyRequestAttendeeSelector.displayName = 'MoneyRequestAttendeeSelector';
-export default memo(MoneyRequestAttendeeSelector, (prevProps, nextProps) => deepEqual(prevProps.attendees, nextProps.attendees) && prevProps.iouType === nextProps.iouType);
+export default memo(MoneyRequestAttendeeSelector, (prevProps, nextProps) => deepEqual(prevProps, nextProps) && prevProps.iouType === nextProps.iouType);
diff --git a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
index fecbd255d053..785c5db795c9 100644
--- a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
+++ b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
@@ -176,6 +176,7 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
!shouldUseNarrowLayout
? {
...styles.sidebarPopover,
+ ...styles.pv6,
...styles.pv4,
}
: styles.pt5,
diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx
index 0715791b6c3f..2dbc4b1e5776 100644
--- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx
+++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx
@@ -95,44 +95,43 @@ function WorkspaceWorkflowsApprovalsApproverPage({policy, personalDetails, isLoa
.map((employee): SelectionListApprover | null => {
const email = employee.email;
- if (!email) {
- return null;
- }
-
- if (!isDefault && policy?.preventSelfApproval && membersEmail?.includes(email)) {
- return null;
- }
+ if (email) {
+ if (!isDefault && policy?.preventSelfApproval && membersEmail?.includes(email)) {
+ return null;
+ }
- // Do not allow the same email to be added twice
- const isEmailAlreadyInApprovers = approversFromWorkflow?.some((approver, index) => approver?.email === email && index !== approverIndex);
- if (isEmailAlreadyInApprovers && selectedApproverEmail !== email) {
- return null;
- }
+ // Do not allow the same email to be added twice
+ const isEmailAlreadyInApprovers = approversFromWorkflow?.some((approver, index) => approver?.email === email && index !== approverIndex);
+ if (isEmailAlreadyInApprovers && selectedApproverEmail !== email) {
+ return null;
+ }
- // Do not allow the default approver to be added as the first approver
- if (!isDefault && approverIndex === 0 && defaultApprover === email) {
- return null;
- }
+ // Do not allow the default approver to be added as the first approver
+ if (!isDefault && approverIndex === 0 && defaultApprover === email) {
+ return null;
+ }
- const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(employeeList);
- const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? '');
- const {avatar, displayName = email, login} = personalDetails?.[accountID] ?? {};
+ const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(employeeList);
+ const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? '');
+ const {avatar, displayName = email, login} = personalDetails?.[accountID] ?? {};
- return {
- text: displayName,
- alternateText: email,
- keyForList: email,
- isSelected: selectedApproverEmail === email,
- login: email,
- icons: [{source: avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: displayName, id: accountID}],
- rightElement: (
-
- ),
- };
+ return {
+ text: displayName,
+ alternateText: email,
+ keyForList: email,
+ isSelected: selectedApproverEmail === email,
+ login: email,
+ icons: [{source: avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: displayName, id: accountID}],
+ rightElement: (
+
+ ),
+ };
+ }
+ return null;
})
.filter((approver): approver is SelectionListApprover => !!approver);
diff --git a/tests/ui/ReportActionAvatarsTest.tsx b/tests/ui/ReportActionAvatarsTest.tsx
index 0389c455e049..376594a462bd 100644
--- a/tests/ui/ReportActionAvatarsTest.tsx
+++ b/tests/ui/ReportActionAvatarsTest.tsx
@@ -54,14 +54,13 @@ const parseSource = (source: AvatarSource | IconAsset): string => {
};
jest.mock('@src/components/Avatar', () => {
- return ({source, name, avatarID, testID = 'Avatar'}: AvatarProps) => {
+ return ({source, testID = 'Avatar', ...rest}: AvatarProps) => {
return (