Skip to content

refactor: decompose IOURequestStepScan into variant components#85571

Draft
adhorodyski wants to merge 21 commits intoExpensify:mainfrom
callstack-internal:decompose-scan-screens-plan
Draft

refactor: decompose IOURequestStepScan into variant components#85571
adhorodyski wants to merge 21 commits intoExpensify:mainfrom
callstack-internal:decompose-scan-screens-plan

Conversation

@adhorodyski
Copy link
Contributor

@adhorodyski adhorodyski commented Mar 17, 2026

Explanation of Change

IOURequestStepScan was a single large screen component with ~27 reactive Onyx subscriptions that handled every scan variant (global create, from report, edit receipt, skip confirmation) in one place. This made it expensive to render and hard to reason about — every variant subscribed to data it didn't need.

This PR decomposes the screen into four focused variant components: ScanGlobalCreate, ScanFromReport, ScanEditReceipt, and ScanSkipConfirmation. Each variant only subscribes to the Onyx keys it actually needs, reducing the common-path subscription count from ~27 to ~5.

Supporting changes:

  • Rewrote both index.tsx (web) and index.native.tsx (native) as thin routers that select and render the correct variant
  • Deleted the useReceiptScan god hook and handleMoneyRequestStepScanParticipants god function; logic is now co-located with each variant
  • Extracted a unified Camera component with platform variants (index.tsx / index.native.tsx)
  • Extracted GpsPermissionGate from ScanSkipConfirmation into its own component
  • Extracted shared scan utilities used across variants
  • Moved the test receipt flow to the parent (IOURequestStartPage), eliminating the onLayout bridge
  • Flattened handler chains and improved naming (onCapturevalidateFilesonFilesAccepted)
  • Added selectors to policy/derived hooks to prevent unnecessary re-renders
  • Exported getAllTransactionDrafts and getAllReportNameValuePairs getters from the IOU actions layer
  • Deleted the duplicate index.native.tsx router that was left over after the rewrite

No user-visible behavior changes are intended.

Fixed Issues

$ #85583
PROPOSAL:

Tests

  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

adhorodyski and others added 21 commits March 16, 2026 16:40
…-renders

Add Onyx selectors to usePolicyForMovingExpenses, useDefaultExpensePolicy,
and useReportAttributes so they return stable values and don't trigger
re-renders when unrelated policies or reports change.

- usePolicyForMovingExpenses: selector computes qualifying policy IDs + flags,
  per-key lookup for the actual policy object
- useDefaultExpensePolicy: selector finds single group policy ID,
  per-key lookup for the policy
- useReportAttributes: new useReportAttributesByID(reportID) hook that
  selects only one report's attributes (cheap deep comparison)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…etters

Add getter functions for module-level caches in IOU/index.ts following the
existing pattern (getAllPersonalDetails, getAllTransactionViolations, etc.).
These will be used by scan variant components to read action-layer data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…canGlobalCreate, ScanSkipConfirmation)

4 self-contained variant components for the scan screen decomposition:
- ScanEditReceipt: simplest, replaceReceipt → goBack
- ScanFromReport: common path, setParticipants → navigateToConfirmation
- ScanGlobalCreate: shouldUseDefaultExpensePolicy → determine target → navigate
- ScanSkipConfirmation: heavy path, inline requestMoney/trackExpense/startSplitBill

Each variant receives zero props, reads route params via useRoute(),
fetches own Onyx data via hooks. ScanFromReport has zero heavy
subscriptions. ScanSkipConfirmation uses selector-optimized hooks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the 210-line component + useReceiptScan mega-hook with a thin router
that determines the scan variant (ScanEditReceipt, ScanFromReport,
ScanGlobalCreate, ScanSkipConfirmation) and renders it with zero props.

Router subscribes only to per-key data needed for branching:
skipConfirmation, reportNameValuePairs, usePolicy. Each variant
reads its own route params and Onyx data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the 680-line monolithic component (vision-camera, gestures,
animations, flash, shutter, multi-scan) with the same thin router
pattern as the web version.

Router determines the variant and renders it with zero props.
Native camera code is now handled by the variant components which
render MobileWebCameraView/DesktopWebUploadView (the native-specific
Camera extraction is a follow-up).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… + cleanup

Remove the 250-line god function from MoneyRequest.ts — its logic is now
inlined in the variant components. Delete the useReceiptScan mega-hook
and its test. Clean up unused imports and test helpers.

Deleted:
- handleMoneyRequestStepScanParticipants (MoneyRequest.ts)
- MoneyRequestStepScanParticipantsFlowParams type
- InitialTransactionParams type
- useReceiptScan.ts hook
- useReceiptScan.test.ts
- handleMoneyRequestStepScanParticipants tests in MoneyRequestTest.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generic capture device with onCapture(file, source). Builds on existing
NavigationAwareCamera. No business logic, no IDs, no multi-scan.

- Camera/types.ts: CameraProps interface
- Camera/FileUpload.tsx: desktop drag-drop + file picker
- Camera/CameraCapture.tsx: mobile web viewfinder + shutter + flash + gallery
- Camera/index.tsx: web entry, routes mobile→CameraCapture, desktop→FileUpload
- Camera/index.native.tsx: vision-camera + focus gesture + flash + gallery

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace MobileWebCameraView/DesktopWebUploadView renders with
<Camera onCapture={handleCapture} />. Each variant composes Camera +
StepScreenWrapper + LocationPermissionModal + ErrorModal via JSX.

Removes:
- isMobile() branching (Camera handles internally)
- 20+ prop threading to camera views
- Direct MobileWebCameraView/DesktopWebUploadView imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the two-step pattern (useOnyx for NVP → isArchivedReport) with
a single useOnyx call using isArchivedReport as a selector. Returns a
stable boolean — prevents re-renders when unrelated NVP fields change.

Update both scan routers and ScanSkipConfirmation to use
useReportIsArchived(reportID) instead of the manual pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ters

The router doesn't use currentUserPersonalDetails — each variant fetches
its own via useCurrentUserPersonalDetails(). Remove the dead HOC wrapper
and clean up the IOURequestStepScanProps type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ScanEditReceipt, ScanFromReport, and ScanGlobalCreate never trigger
the location permission flow (setStartLocationPermissionFlow(true) is
unreachable in all 3). Remove the dead state, modal JSX, GPS pre-fetch
effects, and unused imports.

Only ScanSkipConfirmation actually uses the location permission flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Isolate the GPS location permission modal into a declarative
<GpsLocationGate> component. Parent sets pendingFiles when GPS
permission needs prompting, gate renders LocationPermissionModal
and calls onResolved when granted/denied.

Removes inline location permission state + modal JSX from
ScanSkipConfirmation. The Onyx subscription stays in the parent
to decide the code path; the gate only handles the modal rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pling

GpsPermissionGate is a pure permission component:
- active: boolean (whether the prompt is showing)
- onResolved(granted: boolean) (result callback)

No files, no receipts, no business concepts. The caller decides
what data to attach to the permission result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract 6 focused utilities from duplicated variant code (~150 lines saved):
- getFileSource: file → URL conversion
- bridgeCameraToValidation: Camera onCapture → validateFiles bridge
- startScanProcessSpan: telemetry span creation
- useScanFileReadabilityCheck: mount effect for file readability
- buildReceiptFiles: multi-file receipt processing loop
- createTestReceiptHandler: factory for test receipt handler

Fix onLayout wiring: router now passes onLayout from IOURequestStartPage
through to variants. Each variant exposes setTestReceiptAndNavigate via
<View onLayout={() => onLayout?.(testReceiptHandler)}>. Removes the
@typescript-eslint/no-unused-vars suppression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After decomposition, index.tsx and index.native.tsx were byte-for-byte
identical — all platform-specific code now lives in Camera/index.native.tsx
and the variant components. RN platform resolution falls through to index.tsx.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createTestReceiptHandler was forcing a 3-param signature
(files, locationPermissionGranted, isTestTransaction) onto every variant,
even though isTestTransaction was dead code everywhere and
locationPermissionGranted was only used by ScanSkipConfirmation.

Flip the dependency: createTestReceiptHandler now accepts a simple
(files: ReceiptFile[]) => void callback. Each variant's
navigateToConfirmationStep only declares params it actually uses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The onLayout callback parameter was named setTestReceiptAndNavigate,
leaking test infrastructure details into the component contract.
Renamed to generic handler: () => void across all variants and caller.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test receipt feature used an inverted control flow: child variants
registered a handler upward via onLayout (abusing a View layout event),
parent stored it in a ref, tooltip onConfirm called the ref. This
violated PERF-10, PERF-8, and CLEAN-REACT-PATTERNS-4.

Move the entire test receipt flow inline into the tooltip's onConfirm
handler in IOURequestStartPage. The parent already has transactionID,
iouType, reportID — all action functions are callable directly.

Also restores McTest participant assignment that was lost when
handleMoneyRequestStepScanParticipants was deleted.

Deleted: createTestReceiptHandler.ts, onLayout prop from all variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename handleCapture → onCapture, processReceipts → onFilesAccepted.
Inline bridgeCameraToValidation (3-line shim) into onCapture.
Inline navigateToConfirmationStep into onFilesAccepted where simple.

The capture flow now reads as 3 clear steps:
  onCapture → validateFiles (hook) → onFilesAccepted

Deleted: bridgeCameraToValidation.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace nested if/else with early return guard clause for
!shouldUseDefaultExpensePolicy. Reduces nesting by one level.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adhorodyski
Copy link
Contributor Author

Context

Branch decompose-scan-screens-plan decomposes the monolithic IOURequestStepScan (682-line native + 209-line web) into a thin router + 4 variant components + a generic Camera component. The primary motivation is eliminating camera jank caused by ~27 Onyx subscriptions firing on global events while the camera is active.

Onyx Subscription Reduction (the big win)

The old monolith loaded ~15 useOnyx subscriptions on every mount, regardless of which scan flow the user was in. Every global Onyx event (chat message, report update, etc.) could trigger re-renders in the camera screen — the root cause of camera jank.

After decomposition, each variant only subscribes to what it needs:

Flow Old subs New subs Reduction
ScanEditReceipt (backTo/edit) 15 4 -73%
ScanFromReport (normal) 15 3 -80%
ScanGlobalCreate (FAB create) 15 3 -80%
ScanSkipConfirmation 15 16 ~same (but lazy)

The first three flows are the most common user paths. ScanSkipConfirmation legitimately needs the data, but it only loads when that specific path is active — previously all 15 subscriptions fired for everyone.

God Function Elimination

handleMoneyRequestStepScanParticipants — a 34-parameter, 250-line function in MoneyRequest.ts — is deleted. Its logic is now inlined in the specific variant that uses it. This eliminates the overhead of assembling and passing 34 arguments on every invocation, and removes 321 lines from the shared action file.

React Compiler Alignment

Old code had 5 manual useCallback/useMemo calls. New code has zero — React Compiler handles all memoization. This means fewer object allocations per render and better compiler optimization paths.

What This Means in Practice

  • Camera jank reduction: Fewer Onyx subscriptions = fewer spurious re-renders while the camera is active. Global events (new chat, report changes) won't trigger re-renders in ScanFromReport/ScanEditReceipt.
  • Faster mount: Each variant mounts with only the hooks it needs instead of hydrating 15 Onyx keys.
  • No regression for skip-confirmation: The heaviest flow (ScanSkipConfirmation) keeps all its data but is now isolated — it doesn't slow down the common paths.

Summary

~80% of scan screen users (edit, from-report, global-create) get significantly fewer re-renders and faster mounts. The skip-confirmation path is functionally equivalent but no longer penalizes everyone else.

@adhorodyski
Copy link
Contributor Author

Split Plan: IOURequestStepScan Decomposition

Context

Branch decompose-scan-screens-plan decomposes the monolithic IOURequestStepScan (web: 740 lines, native: 677 lines, 27 Onyx subscriptions) into a router + 4 zero-prop variants + generic Camera component pattern. The god function handleMoneyRequestStepScanParticipants (30+ params, 260 lines) is eliminated. Each variant reads its own route params and Onyx data, reducing re-renders and camera jank.

The branch also includes hook selector optimizations that serve as prerequisites.

20 author commits (excluding already-merged useIsInSidePanel), ~3600 lines added / ~2200 lines removed.


Dependency Graph

PR 1 (selectors + getters) ──────┐
                                  ├── PR 3 (scan decomposition) ── PR 4 (dead code cleanup)
PR 2 (Camera component) ─────────┘

PR 1 and PR 2 can land in parallel. PR 3 depends on PR 1 + PR 2. PR 4 depends on PR 3.


PR 1: Hook selector optimizations + IOU getters

Title: perf: add selectors to derived hooks, export IOU getters
~170 lines | 5 files | independent (prerequisite for PR 3)

Adds selectors to prevent full-collection re-renders in hooks used by the scan router and variants. Exports two getter functions from IOU actions that variants need to access module-level caches.

Files

  • src/hooks/useReportIsArchived.ts
  • src/hooks/useDefaultExpensePolicy.tsx
  • src/hooks/usePolicyForMovingExpenses.ts
  • src/hooks/useReportAttributes.ts
  • src/libs/actions/IOU/index.ts (just the getter exports)

Commits

  1. perf: add selectors to policy/derived hooksuseReportIsArchived: use isArchivedReport selector for stable boolean instead of full report object. useDefaultExpensePolicy: selector finds single policy ID from collection. usePolicyForMovingExpenses: selector computes IDs + flags. Add useReportAttributesByID helper.
  2. feat: export getAllTransactionDrafts and getAllReportNameValuePairs — Two getter functions in IOU/index.ts that expose module-level Onyx caches. Follows existing pattern (getAllPersonalDetails, getAllTransactionViolations).

PR 2: Camera component extraction

Title: feat: extract generic Camera component with platform variants
~1050 lines | 5 new + 6 renamed files | independent (prerequisite for PR 3)

Extracts a unified Camera component with a clean onCapture(file, source) callback interface. Platform-specific implementations: native (VisionCamera), mobile web (CameraCapture with viewfinder), desktop web (FileUpload drag-and-drop).

Also moves NavigationAwareCamera/ and ReceiptPreviews/ under components/ for consistent structure.

Files (new)

  • src/pages/iou/request/step/IOURequestStepScan/components/Camera/index.tsx
  • src/pages/iou/request/step/IOURequestStepScan/components/Camera/index.native.tsx
  • src/pages/iou/request/step/IOURequestStepScan/components/Camera/CameraCapture.tsx
  • src/pages/iou/request/step/IOURequestStepScan/components/Camera/FileUpload.tsx
  • src/pages/iou/request/step/IOURequestStepScan/components/Camera/types.ts

Files (renamed)

  • NavigationAwareCamera/{Camera,WebCamera,types}.tsxcomponents/NavigationAwareCamera/
  • ReceiptPreviews/{index,SubmitButtonShadow/*}.tsxcomponents/ReceiptPreviews/

Commits

  1. refactor: move NavigationAwareCamera and ReceiptPreviews into components/ — Pure renames, update import paths in current index.tsx / index.native.tsx on main.
  2. feat: add Camera component with platform variants — 5 new files. Camera/index.tsx dispatches mobile → CameraCapture, desktop → FileUpload. Camera/index.native.tsx wraps VisionCamera. types.ts defines CameraProps { onCapture, shouldAcceptMultipleFiles?, onLayout? }.

Risk

  • CameraCapture references NavigationAwareCamera/WebCamera — ensure rename from commit 1 is compatible.
  • Native Camera uses react-native-vision-camera — verify import paths match repo's existing dependency.

PR 3: Scan decomposition — variants + router rewrite

Title: refactor: decompose IOURequestStepScan into router + 4 variants
~2500 lines | ~20 files | depends on PR 1 + PR 2

This is the core decomposition. Adds 4 self-contained variant components + shared utilities, then replaces the monolithic router with a thin ~50-line branching component. Deletes index.native.tsx (unified to single index.tsx).

New files — utilities

  • utils/buildReceiptFiles.ts — Builds ReceiptFile[] from files + params, sets receipt on transactions
  • utils/getFileSource.ts — Platform abstraction: file.uri ?? URL.createObjectURL(file)
  • utils/startScanProcessSpan.ts — Telemetry span helper
  • utils/useScanFileReadabilityCheck.ts — Verifies local files are readable on mount

New files — components

  • components/GpsPermissionGate.tsx — Pure permission gate: { active, onResolved(granted) }
  • components/ScanEditReceipt.tsx (~106 lines) — Replace receipt → navigate back
  • components/ScanFromReport.tsx (~119 lines) — Set participants from report → confirmation
  • components/ScanGlobalCreate.tsx (~158 lines) — Auto-reporting logic → navigate
  • components/ScanSkipConfirmation.tsx (~369 lines) — Direct submit, GPS gate, split bill

Modified files

  • index.tsx — Rewrite to thin router (51 lines)
  • index.native.tsx — Delete (unified router)
  • types.ts — Updated types for variants
  • src/pages/iou/request/IOURequestStartPage.tsx — Accept test receipt flow moved here

Variant routing logic

if (backTo || isEditing)           → ScanEditReceipt
if (!globalCreate && !archived)
  if (shouldSkipConfirmation)      → ScanSkipConfirmation
  else                             → ScanFromReport
else                               → ScanGlobalCreate

Commits (ordered for clean bisectability)

  1. feat: add shared scan utilitiesbuildReceiptFiles, getFileSource, startScanProcessSpan, useScanFileReadabilityCheck
  2. feat: add GpsPermissionGate component — Extract GPS permission modal wrapper
  3. feat: add scan variant components — ScanEditReceipt, ScanFromReport, ScanGlobalCreate, ScanSkipConfirmation (import Camera from PR 2, use selectors from PR 1)
  4. refactor: rewrite IOURequestStepScan as thin router — Replace monolithic index.tsx with ~50-line router importing variants. Delete index.native.tsx.
  5. refactor: move test receipt flow to IOURequestStartPage — Eliminate onLayout bridging between router and variants
  6. refactor: remove dead params from navigateToConfirmationStep — Clean variant signatures
  7. refactor: flatten capture handler chain, improve naming — Final code cleanup in variants

Risk

  • This is the largest PR. Consider having a reviewer focus on the router logic first, then variants individually.
  • ScanSkipConfirmation is the most complex variant (~370 lines, 18 Onyx subscriptions) — warrants extra review attention.
  • Unified index.tsx (no .native.tsx) must work on both platforms — verify with native build.

PR 4: Dead code cleanup

Title: chore: delete handleMoneyRequestStepScanParticipants and useReceiptScan
~1440 lines deleted | 4 files | depends on PR 3

Removes the god function and hook that the monolithic scan screen used, now fully replaced by variant components.

Files

  • src/libs/actions/IOU/MoneyRequest.ts — Delete handleMoneyRequestStepScanParticipants function + MoneyRequestStepScanParticipantsFlowParams type
  • src/pages/iou/request/step/IOURequestStepScan/useReceiptScan.ts — Delete entirely
  • tests/actions/IOU/MoneyRequestTest.ts — Delete ~484 lines of tests for removed function
  • tests/unit/hooks/useReceiptScan.test.ts — Delete entirely (~395 lines)

Commits

  1. chore: delete handleMoneyRequestStepScanParticipants + type — Remove from MoneyRequest.ts, update exports in MoneyRequest.ts
  2. chore: delete useReceiptScan hook — Remove useReceiptScan.ts
  3. chore: delete tests for removed scan code — Remove test files/sections

@marcaaron marcaaron self-requested a review March 17, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant