fix: recover Search snapshots stuck on a stale error instead of looping on Try again - #98412
Conversation
…apshot-blocks-recovery
|
@DylanDylann 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] |
|
@codex review |
| // The Search page then renders its error view with nothing in flight, and only the Try again | ||
| // button can break out of it. Allow one recovery attempt per hash instead, so a transient failure | ||
| // heals by itself while a persistent one still settles on the error view rather than looping | ||
| // request -> failure -> request. |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
Comments should read as plain, natural sentences. This newly added comment uses -> to express a relationship (looping request -> failure -> request) instead of writing the relationship out in words, which the plain-comment-style rule specifically calls out.
Rewrite the relationship in words, for example:
// heals by itself while a persistent one still settles on the error view rather than looping
// from request to failure and back to request.Reviewed at: 5c39077 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@MelvinBot review |
ReviewVerdict: The fix is correct, well-scoped, and thoroughly tested. It targets the real trap — I confirmed the two load-bearing assumptions the fix rests on:
The loop-prevention reasoning is sound end to end: recovery fires → optimistic clears Non-blocking observations
TestsCoverage is excellent — the new Nice, careful work — the inline comments make the non-obvious Onyx/effect interactions easy to follow. |
|
Both observations are handled. The errors check now uses the same non empty rule as the error view, so the two gates cannot drift apart. On the response codes, yes that is intentional. 401 is the only code where the server has judged the query itself, so re sending it can never succeed. Every other code is a failure of that one attempt, and a single retry is exactly what recovers it. The Set caps it at one extra request either way, so a broader deny list would only cost us recoveries without preventing anything. |
Explanation of Change
The Search snapshot for a query stores an
errorsobject when a request fails. Normally the next request's optimistic data clears it. Two places both treat a presenterrorsobject as a sign the snapshot is resolved:isSearchDataLoadedinSearchUIUtils.ts, which gates the effect inuseSearchPageSetup.tsthat fires the next request, and the early return inSearch/index.tsxwhilehasErrorsis true. Each check is reasonable on its own, but together they trap a query. One failed request writeserrorsto disk, and after that nothing fires a new request to clear it. The Search page renders its error view on every mount, with nothing in flight, and only the Try again button breaks the loop. Because the snapshot lives on disk, this survives navigation and app restarts.The fix lives entirely in
useSearchPageSetup.ts. A snapshot whose only problem iserrorsno longer blocks the request on mount. A ref remembers which hashes already got a retry this mount, so a persistent failure still settles on the error view instead of looping request, failure, request forever. Queries the server already rejected as malformed (responseJsonCodeofINVALID_SEARCH_QUERY) are skipped, since sending them again cannot help.SearchUIUtils.tsandSearch/index.tsxare not changed. The page level request already clearserrorsbefore the component reaches its error branch, so neither file needs to change for the fix to work.Fixed Issues
$ #98312
PROPOSAL:
Tests
command === 'Search'at the top ofxhr()insrc/libs/HttpUtils.ts.Please do not test this by turning the network off.
hasErrorsis gated on!isOffline, so going offline hides the error view and the bug never shows up.Offline tests
N/A
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
after-fix.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari