Skip to content

LF- 5213: Farm notes offline support#4108

Merged
kathyavini merged 27 commits intointegrationfrom
LF-5213/Farm_notes_offline_support
Apr 13, 2026
Merged

LF- 5213: Farm notes offline support#4108
kathyavini merged 27 commits intointegrationfrom
LF-5213/Farm_notes_offline_support

Conversation

@SayakaOno
Copy link
Copy Markdown
Collaborator

@SayakaOno SayakaOno commented Apr 8, 2026

Description

  • Update API error handling in the components
    • Prevent error snackbars from showing for network errors. (network errors are handled in the API slices)
  • Add optimistic UI for farm notes and the farm notes unread dot
  • Update getFarmNotes endpoint
    • When offline, queryFn returns the cached data to keep the query in "fulfilled" status; otherwise optimistic updates won't appear on the UI once the query moves to "rejected".
  • Sync farm notes
    • Update useServiceWorkerListener to handle farm note routes, consolidating snackbar messages in one place.
    • Add farm note routes to RETRY_ROUTES in sw.js.

Jira link: https://lite-farm.atlassian.net/browse/LF-5213

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Passes test case
  • UI components visually reviewed on desktop view
  • UI components visually reviewed on mobile view
  • Other (please explain)

Checklist:

  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The precommit and linting ran successfully
  • I have added or updated language tags for text that's part of the UI
  • I have ordered translation keys alphabetically (optional: run pnpm i18n to help with this)
  • I have added the GNU General Public License to all new files

@SayakaOno SayakaOno self-assigned this Apr 8, 2026
@SayakaOno SayakaOno added enhancement New feature or request new translations New translations to be sent to CrowdIn are present labels Apr 8, 2026
Comment on lines -28 to -29
onSuccess: () => void;
onCancel: () => void;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Renamed onSuccess and onCancel to a single onClose callback to make it clearer why it's called after a network error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Extracted snackbar messages and the function into a separate file. Let me know if I broke your original intent!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for making it so much cleaner + more extensible ❤️ It had become truly behemoth since that original file!

@SayakaOno SayakaOno marked this pull request as ready for review April 8, 2026 22:02
@SayakaOno SayakaOno requested review from a team as code owners April 8, 2026 22:02
@SayakaOno SayakaOno requested review from kathyavini and removed request for a team April 8, 2026 22:02
Copy link
Copy Markdown
Collaborator

@kathyavini kathyavini left a comment

Choose a reason for hiding this comment

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

Thank you so much for taking on this development task, and for figuring out all the nuances of working with RTK Query for this!! Just a few hours of pnpm build + pnpm preview testing reminded me OFFLINE IS SO HARD! Thank you for establishing the patterns we can use going forward 🙏

The code looked excellent, and the only bug I could spot was in the isNetworkError function, which was giving false positive to all API errors.

"ONLINE": "Note updated. Will save when you're back online",
"SUCCESS": "Note updated offline has been saved successfully"
},
"FAILED": "Task changes failed to save",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"Note changes failed to save?"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for making it so much cleaner + more extensible ❤️ It had become truly behemoth since that original file!

'farm_notes.create': {
successMessage: i18n.t('message:FARM_NOTE.SYNC.ADD.SUCCESS'),
errors: {},
retryMessage: i18n.t('message:FARM_NOTE.SYNC.ADD.NETWORK_ERROR'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Were you at all bothered by the task pattern being <FEATURE>.<ACTION>.SYNC and this one being <FEATURE>.SYNC.<ACTION>? 😅

I guess it's probably easier to group the SYNC messages together top-level so that should be our pattern going forward, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think so; it made more sense to me.

The task pattern uses <FEATURE> as the top-level key, but in farm note, it's already nested under SUCCESS/ERROR, so adding <FEATURE> felt redundant (here's how farm note looks with the task pattern).
If I'm missing something, please let me know!

try {
await queryFulfilled;
} catch (error: any) {
if (isNetworkError(error)) {
Copy link
Copy Markdown
Collaborator

@kathyavini kathyavini Apr 9, 2026

Choose a reason for hiding this comment

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

Edit: how did this comment lose it's formatting??! 😅 Lost its link too

In tasks, we only show the to-be-synced snackbar when offline, not in the case of the API being down (which is also a network error). Here the to-be-synced when online would be shown in both cases. I think the original concern from tasks (two snackbars) doesn't exist anymore now that the queue is always replayed manually, though? 🤔

Should we remove the if (isOffline) check in the task sagas now so these two features behave equivalently?

In an ideal world, I think in both places we would use the NETWORK_ERROR string if the user is online, and the ONLINE string if the user is offline. Do you think that's worth the refactor in tasks? Or is it too much of edge case? I don't love where tasks ended up... when the API is down you get:

  1. optimistic update
  2. no snackbar explaining it
  3. and then (on replay of the queue) the series of "task ____ when offline" snackbars! 😬

Here you get the wrong (offline) snackbars, but it's still less confusing!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for bringing this up, I didn't remember the discussion 🙏

In an ideal world, I think in both places we would use the NETWORK_ERROR string if the user is online, and the ONLINE string if the user is offline.

Let me quickly update it!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, the context aware-snackbars ARE so pleasing!! I love them.

if (isNetworkError(error)) {
dispatch(enqueueSuccessSnackbar(i18n.t('message:FARM_NOTE.SYNC.ADD.ONLINE')));
} else {
// Server error: rollback the optimistic updateconsole.error(error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the console.error(error) meant to be active / on its own line?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🫨 The component logs it, so it shouldn't be here. Thank you for catching 🙏🙏🙏

Comment thread packages/webapp/src/util/apiUtils.ts Outdated

export type QueryResult<T> = { data: T } | { error: FetchBaseQueryError };

// In RTK Query, network errors result in undefined status or specific error structure
Copy link
Copy Markdown
Collaborator

@kathyavini kathyavini Apr 9, 2026

Choose a reason for hiding this comment

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

In which case were you able to create an error with undefined status? I saw both the offline case and the API down case matching the type of FetchBaseQueryError (the one imported in this file) which always had a status of 'FETCH_ERROR`!

Additionally, although this works in the component context (with .unwrap), when it's called from onQueryStarted, it is giving a false positive to every API error, because status is nested within error like this for a regular HTTP error:

Image

So regular API errors like 404 were also giving the sync snackbar (from onQueryStarted) and then the regular error snackbar from the component:

Screen.Recording.2026-04-09.at.12.39.16.PM.mov

I think the logic can be updated like this:

export const isNetworkError = (error: any) => {
  const status = error?.error?.status ?? error?.status;
  return status === 'FETCH_ERROR';
};

but again I wasn't able to identify an undefined status state (which this would not work for)...!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm so sorry, that was an AI hallucination I didn’t catch 😭
Thank you so much!!!

Copy link
Copy Markdown
Collaborator Author

@SayakaOno SayakaOno left a comment

Choose a reason for hiding this comment

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

Thank you so much for reviewing!
I addressed your comments and added storeActivity('/', 'open_farm_notes') to record the event in offline_event_log table.
I'd appreciate another review 🙏

'farm_notes.create': {
successMessage: i18n.t('message:FARM_NOTE.SYNC.ADD.SUCCESS'),
errors: {},
retryMessage: i18n.t('message:FARM_NOTE.SYNC.ADD.NETWORK_ERROR'),
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think so; it made more sense to me.

The task pattern uses <FEATURE> as the top-level key, but in farm note, it's already nested under SUCCESS/ERROR, so adding <FEATURE> felt redundant (here's how farm note looks with the task pattern).
If I'm missing something, please let me know!

if (isNetworkError(error)) {
dispatch(enqueueSuccessSnackbar(i18n.t('message:FARM_NOTE.SYNC.ADD.ONLINE')));
} else {
// Server error: rollback the optimistic updateconsole.error(error);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🫨 The component logs it, so it shouldn't be here. Thank you for catching 🙏🙏🙏

try {
await queryFulfilled;
} catch (error: any) {
if (isNetworkError(error)) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for bringing this up, I didn't remember the discussion 🙏

In an ideal world, I think in both places we would use the NETWORK_ERROR string if the user is online, and the ONLINE string if the user is offline.

Let me quickly update it!

Comment thread packages/webapp/src/util/apiUtils.ts Outdated

export type QueryResult<T> = { data: T } | { error: FetchBaseQueryError };

// In RTK Query, network errors result in undefined status or specific error structure
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm so sorry, that was an AI hallucination I didn’t catch 😭
Thank you so much!!!

Copy link
Copy Markdown
Collaborator

@kathyavini kathyavini left a comment

Choose a reason for hiding this comment

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

Looks fantastic!! Thank you!

@kathyavini kathyavini added this pull request to the merge queue Apr 13, 2026
Merged via the queue into integration with commit bb7fe45 Apr 13, 2026
4 checks passed
@SayakaOno SayakaOno deleted the LF-5213/Farm_notes_offline_support branch April 13, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request new translations New translations to be sent to CrowdIn are present

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants