Skip to content

Remove OnyxUtils.get() from GPSTripStateChecker (use useOnyx) - #98703

Open
MelvinBot wants to merge 1 commit into
mainfrom
claude-removeOnyxUtilsGpsTripStateChecker
Open

Remove OnyxUtils.get() from GPSTripStateChecker (use useOnyx)#98703
MelvinBot wants to merge 1 commit into
mainfrom
claude-removeOnyxUtilsGpsTripStateChecker

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

OnyxUtils.get() (imported from the internal deep path react-native-onyx/dist/OnyxUtils) is not a sanctioned way to read Onyx data. GPSTripStateChecker/index.native.tsx was using it inside its mount effect to re-read GPS_DRAFT_DETAILS, even though the component already subscribes to the same key via useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS).

This change removes the deep import and serves the app-restart check from the existing useOnyx subscription:

  • Removed import OnyxUtils from 'react-native-onyx/dist/OnyxUtils' and the OnyxUtils.get(...) call.
  • Captured the subscription metadata (const [gpsDraftDetails, gpsDraftDetailsMetadata] = useOnyx(...)) and gate the restart effect on gpsDraftDetailsMetadata.status === 'loaded'. Gating on the loaded status (rather than reading the value eagerly) matters so we don't misread the not-yet-hydrated state as "no trip" and wrongly stop an in-progress trip's background task.
  • Used a useRef flag so the restart handler still runs exactly once per mount, even though the subscription value can change later in the session.
  • Kept the unmount cleanup (which stops orphaned location tracking) in its own []-dependency useEffect so it still runs only on unmount.

Note: this component is native-only — the web build (index.tsx) renders null — so this change has no effect on web.

Fixed Issues

$ #98609
PROPOSAL: #98609 (comment)

Tests

  1. Start a GPS distance trip.
  2. Kill and relaunch the app.
  3. Verify the "Continue trip" modal is prompted on restart.
  4. Separately, with no active trip but a leftover background location task, relaunch and verify the orphaned task is stopped.
  • Verify that no errors appear in the JS console

Offline tests

Same as Tests

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 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)
  • 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)
  • 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.
  • 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.

…tion

Co-authored-by: mkhutornyi <mkhutornyi@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I checked the CI on this PR — there's no code-related failure to fix. When your request came in, several checks (typecheck, branch-perf-tests, baseline-perf-tests) were still pending; they have all since finished and passed.

The only remaining red check is Check independent approval, which isn't a code/test failure. It fails simply because the PR hasn't received an approval from an independent reviewer yet (this PR's author is MelvinBot, so it can't self-approve). It will go green once a reviewer approves — no push needed.

Summary: all build/lint/type/test/perf checks are green; nothing to patch. This PR just needs a human review + approval to clear the last check.

@mkhutornyi

Copy link
Copy Markdown
Contributor

@codex review

@mkhutornyi

Copy link
Copy Markdown
Contributor

@MelvinBot review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4f4ddd6a0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const gpsTrip = await OnyxUtils.get(ONYXKEYS.GPS_DRAFT_DETAILS);

if (!gpsTrip?.isTracking) {
if (!gpsDraftDetails?.isTracking) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-read GPS state after notification cleanup

On iOS, checkAndCleanGpsNotification() awaits liveActivityManager.listAll(), so this closure can become stale while it is suspended. If the hydrated state is not tracking and the user starts or resumes a trip before that call finishes, this branch still sees the old false value, detects the newly started background task, and stops it while Onyx remains isTracking: true, causing the trip to stop collecting locations. The removed OnyxUtils.get() ran after this await and observed the latest state; retain the latest subscribed value in a ref or otherwise verify the current tracking state after cleanup before stopping the task.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not something introduced by this PR so I'd leave as is.
@MelvinBot confirm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly agree it's fine to leave as-is, with one nuance so the call is made with eyes open.

The check-then-act race itself is pre-existing. Even before this PR, once the handler read isTracking: false there was still an await (hasStartedLocationUpdatesAsync, index.native.tsx:51) before stopLocationUpdatesAsync, index.native.tsx:53 — a trip started in that gap would have been wrongly stopped too. So the class of race isn't new.

But strictly, this PR does slightly widen the window. The old OnyxUtils.get() read after await checkAndCleanGpsNotification(), whereas the new code reads the closure snapshot captured before it (index.native.tsx:48→50). On iOS that cleanup awaits liveActivityManager.listAll(), index.ios.ts:165, so that native call is now inside the stale window where it wasn't before. Codex's mechanism is technically valid.

Practically, the risk is negligible — so I'm fine leaving it. This is the once-per-mount app-restart handler; the dangerous branch only runs when there's no active trip on launch, and it would require the user to start a brand-new trip during a sub-second startup async window (the continue-trip modal is itself gated behind the splash screen being hidden). Not a realistic sequence.

If you ever want exact parity with the old read-after-cleanup semantics, the minimal change is to read the latest subscribed value from a ref after await checkAndCleanGpsNotification() instead of the pre-await snapshot — but that's optional and arguably outside this refactor's scope. 👍 to leaving as-is.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Review — LGTM ✅

The change faithfully implements the approved proposal, meets every acceptance criterion, and all build/lint/type/test/perf checks are green.

What's correct

  • The deep import react-native-onyx/dist/OnyxUtils and the OnyxUtils.get() call are both gone — no OnyxUtils references remain in the file.
  • The restart check is served from the existing useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS) subscription, gated on gpsDraftDetailsMetadata.status === 'loaded'. This correctly avoids misreading the not-yet-hydrated state as "no trip" — which would have wrongly stopped an in-progress trip's background task.
  • The hasHandledAppRestart ref, set synchronously before the async handler is invoked, keeps the restart handler firing exactly once per mount even though the dep array can re-fire (and even if isTracking changes mid-await).
  • The unmount cleanup that stops orphaned location tracking stays in its own []-dependency useEffect, so it still runs only on unmount.
Behavioral-equivalence check (the one thing worth verifying)

The old code read the value via OnyxUtils.get() after await checkAndCleanGpsNotification(); the new code captures gpsDraftDetails from the subscription before that await. That reordering is safe: checkAndCleanGpsNotification() only stops the native notification service (GPSNotifications/index.android.ts:22) and never mutates GPS_DRAFT_DETAILS, so the value can't change across the await. Both useOnyx and OnyxUtils.get read the same Onyx cache, and the first status === 'loaded' value is the persisted-on-restart value — exactly the snapshot we want. Continue-trip and orphaned-task cleanup paths are preserved.

Testing note: this component is native-only — index.tsx renders null on web and index.native.tsx isn't bundled on web — so the trip-restart logic can't be exercised in a web run and I didn't spawn one. The native repro steps in the Tests/QA section (start a GPS trip → kill & relaunch → confirm the continue-trip modal; plus the orphaned-task cleanup case) should be run by the human co-author before marking this ready for review.

Nothing blocking from me.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Both failing checks are approval/process gates, not code failures — there's nothing to fix in code.

  • Check independent approval — waiting on an independent reviewer's approval.
  • checklist — this is the PR Reviewer Checklist (PR Reviewer Checklist / checklist), which stays red until a reviewer completes their checklist.

Every actual CI check is green: Bun tests, all test jobs, typecheck, ESLint, Oxfmt, React Compiler, Storybook, perf tests, and all builds passed.

Both gates clear once this PR gets a reviewer and an independent approval — no push needed from me.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Regression hunt — none found ✅

I went through the diff adversarially against the old behavior. The change is behaviorally equivalent to the pre-PR code for every app-restart path, and all CI checks are green. Details on each candidate I checked below.

Regression candidates checked & ruled out

1. Value read moved before the await (was after OnyxUtils.get)
Old code read the trip value via await OnyxUtils.get(...) after await checkAndCleanGpsNotification(); new code captures gpsDraftDetails from the subscription before that await. This would only regress if checkAndCleanGpsNotification() mutated GPS_DRAFT_DETAILS. It doesn't on either native platform — iOS only ends Airship live activities and Android only stops the native GpsTripServiceModule. No Onyx write, so the value can't change across the await. ✅

2. Gating on status === 'loaded' could strand the restart check
The old read was unconditional (OnyxUtils.get always resolves), so a worry is that a non-loaded terminal status would skip the check — including the orphaned-background-task cleanup. But react-native-onyx's FetchStatus is 'loading' | 'loaded' only — there's no error/failed terminal state — so 'loaded' is always eventually reached. The check still runs. ✅

3. One-shot semantics / double-fire
hasHandledAppRestart is set synchronously before the async handler is invoked, so even though the effect's dep array ([gpsDraftDetails?.isTracking, gpsDraftDetailsMetadata.status]) can re-fire (e.g. isTracking flips mid-await), the handler body runs exactly once per mount — matching the old []-mount behavior. No double modal, no double cleanup. ✅

4. Snapshot correctness
When status first flips to 'loaded', the gpsDraftDetails in that same render is the hydrated value, read from the same Onyx cache OnyxUtils.get used, and nothing writes GPS_DRAFT_DETAILS between mount and hydration on restart. So the snapshot equals what the old code would have fetched. All three restart outcomes are preserved: in-progress trip → continue-trip modal; no trip + orphaned task → task stopped; unmount → task stopped (now in its own [] effect). ✅

5. Value shape
useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS) (no selector) returns the same raw object OnyxUtils.get returned, and the rest of the component already consumed gpsDraftDetails from that subscription — so ?.isTracking, ?.unit, etc. read identically. ✅

On testing: this component is native-only — index.tsx renders null on web and index.native.tsx isn't bundled on web at all — so there's no web surface to regress and a web run can't exercise this code. The native repro in the PR's Tests/QA section (start a GPS trip → kill & relaunch → confirm the continue-trip modal; plus the orphaned-task cleanup case) is the meaningful check here and should be run on native by the human co-author.

No regressions from me.

@mkhutornyi

Copy link
Copy Markdown
Contributor

Tests

  1. Resume an in-progress trip after an app kill (the main path this PR touches)

    1. Go to + → Create expense → Distance → GPS and tap Start.
    2. Walk/drive far enough that at least a few points are recorded (or use a simulated route).
    3. Force-quit the app from the app switcher (do not stop the trip first).
    4. Relaunch the app and wait for the splash screen to dismiss.
    5. Expected: the "Continue your trip?" modal appears. Tap Continue → you land on the GPS screen, the distance counter picks up from where it left off, and the tracking notification (Android) / Live Activity (iOS) reappears.
    6. Repeat steps 1–5 three times in a row, including once on a high-traffic account. The modal must appear every time — this PR replaced a direct storage read with a subscription-hydration gate, so a flaky/missing modal is the failure mode to watch for.
  2. "View trip" branch

    1. Repeat steps 1–4 above.
    2. On the modal, tap View trip instead.
    3. Expected: the trip stops, you land on the GPS screen with the recorded route and final distance, and the tracking notification / Live Activity is gone. Location tracking is stopped (no persistent location indicator in the status bar).
  3. Clean start — no trip in progress

    1. With no GPS trip started, force-quit and relaunch the app.
    2. Expected: no modal, no tracking notification / Live Activity, no location indicator in the status bar, and no errors in the JS console.
  4. Startup notification cleanup

    1. Start a trip so the tracking notification (Android) / Live Activity (iOS) is visible.
    2. Force-quit and relaunch.
    3. Expected: the stale notification / Live Activity is cleared during startup and only comes back after you tap Continue on the modal. You should never end up with two notifications or two Live Activities.
  5. HybridApp: OldDot → NewDot transition with a trip in progress

    1. Start a GPS trip in NewDot, force-quit.
    2. Relaunch and let the app transition through OldDot into NewDot (sign-in transition path).
    3. Expected: same as test 1 — the modal appears once NewDot is up, and Continue resumes tracking.
  6. Orphaned background task cleanup (optional / best-effort — defensive path, hard to trigger by hand)

    1. Start a trip, then use Settings → Troubleshoot → Import Onyx state to load a state file that has no gpsDraftDetails, and relaunch.
    2. Expected: no modal, and background location tracking is stopped (no persistent location indicator). If you can't manufacture this state, note it as untested rather than passing it.

Offline tests

  1. Turn off the network, start a GPS trip, record a few points, force-quit and relaunch while still offline.
  2. Expected: the "Continue your trip?" modal still appears and Continue resumes tracking — the trip state is read from local storage, so nothing here depends on the network.

QA Steps

Run tests 1–5 above on iOS: Native and Android: Native.

For Android: mWeb Chrome, iOS: mWeb Safari, and MacOS: Chrome / Safari: this component renders null on those platforms, so there is nothing to exercise. Confirm only that the app loads normally and no new errors appear in the JS console.

Regression areas to watch: GPS distance expense creation end-to-end (start → record → stop → save), account switching and delegate switching while a trip is in progress (the "trip in progress" warning modal should still appear and still stop the trip), and switching to Expensify Classic while a trip is in progress.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Still the same two checks failing, and both are approval/process gates — not code failures, so there's nothing for me to push:

  • Check independent approval — waiting on an independent reviewer's approval.
  • checklist — the PR Reviewer Checklist workflow, which stays red until a reviewer completes their checklist.

Every real CI check (Bun tests, all test jobs, typecheck, ESLint, Oxfmt, React Compiler, Storybook, perf, and all builds) is green. Both gates clear once this PR gets a reviewer and an independent approval — no code change is needed from me.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Still nothing to fix here — the only two red checks are approval/process gates, not code failures:

  • Check independent approval — waiting on an independent reviewer's approval.
  • checklist — the PR Reviewer Checklist (PR Reviewer Checklist / checklist), red until a reviewer completes their checklist.

All actual CI (Bun tests, every test job, typecheck, ESLint, Oxfmt, React Compiler, Storybook, perf tests, all builds) is green. Both gates clear once the PR gets a reviewer and an independent approval — no push needed from me.

@mkhutornyi

Copy link
Copy Markdown
Contributor
Details

Explanation of Change

OnyxUtils.get() (imported from the internal deep path react-native-onyx/dist/OnyxUtils) is not a sanctioned way to read Onyx data. GPSTripStateChecker/index.native.tsx was using it inside its mount effect to re-read GPS_DRAFT_DETAILS, even though the component already subscribes to the same key via useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS).

This change removes the deep import and serves the app-restart check from the existing useOnyx subscription:

  • Removed import OnyxUtils from 'react-native-onyx/dist/OnyxUtils' and the OnyxUtils.get(...) call.
  • Captured the subscription metadata (const [gpsDraftDetails, gpsDraftDetailsMetadata] = useOnyx(...)) and gate the restart effect on gpsDraftDetailsMetadata.status === 'loaded'. Gating on the loaded status (rather than reading the value eagerly) matters so we don't misread the not-yet-hydrated state as "no trip" and wrongly stop an in-progress trip's background task.
  • Used a useRef flag so the restart handler still runs exactly once per mount, even though the subscription value can change later in the session.
  • Kept the unmount cleanup (which stops orphaned location tracking) in its own []-dependency useEffect so it still runs only on unmount.

Note: this component is native-only — the web build (index.tsx) renders null — so this change has no effect on web.

Fixed Issues

$ #98609
PROPOSAL: #98609 (comment)

Tests

  1. Start a GPS distance trip.
  2. Kill and relaunch the app.
  3. Verify the "Continue trip" modal is prompted on restart.
  4. Separately, with no active trip but a leftover background location task, relaunch and verify the orphaned task is stopped.
  • Verify that no errors appear in the JS console

Offline tests

Same as Tests

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 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)
  • 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)
  • 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.
  • 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.

@MelvinBot please set the PR body to the content in the details section above

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ Done — I set the PR body to the content you provided (Explanation of Change, Fixed Issues, Tests/Offline/QA, and the completed PR Author Checklist).

@mkhutornyi

Copy link
Copy Markdown
Contributor

@MelvinBot mark as ready for review

@MelvinBot
MelvinBot marked this pull request as ready for review August 17, 2026 06:15
@MelvinBot
MelvinBot requested a review from a team as a code owner August 17, 2026 06:15
@melvin-bot
melvin-bot Bot requested review from flaviadefaria and mkhutornyi and removed request for a team August 17, 2026 06:15
@melvin-bot

melvin-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

@mkhutornyi Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot removed the request for review from a team August 17, 2026 06:15
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — this PR is now marked Ready for review. ✅

One thing to flag: the Tests and QA Steps sections are still placeholders. As the human co-author, please fill those in (suggested native repro steps are already stubbed in the description) so reviewers know exactly what was validated before approval.

@mkhutornyi

mkhutornyi commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (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 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)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
android.mov
Android: mWeb Chrome
iOS: HybridApp
ios.mov
prod.mov
iOS: mWeb Safari
MacOS: Chrome / Safari

@melvin-bot
melvin-bot Bot requested a review from amyevans August 17, 2026 06:16
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Nothing changed since my last comment — the only failing check is Check independent approval, which is an approval gate, not a code failure. It clears once an independent reviewer approves this PR. The review (PR Reviewer Checklist) check is currently re-running and is the same kind of gate.

All real CI checks (tests, typecheck, ESLint, builds, perf) are green, so there's no code for me to fix or push here.

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.

2 participants