Skip to content

[HOLD] Resolve optimistic agent edit route after creation - #96473

Closed
nabi-ebrahimi wants to merge 47 commits into
Expensify:mainfrom
nabi-ebrahimi:fix/disable-pending-agent-edit-navigation
Closed

nabi-ebrahimi wants to merge 47 commits into
Expensify:mainfrom
nabi-ebrahimi:fix/disable-pending-agent-edit-navigation

Conversation

@nabi-ebrahimi

@nabi-ebrahimi nabi-ebrahimi commented Jul 20, 2026 •

Copy link
Copy Markdown

Explanation of Change

Opening a newly created agent before the server finishes creating it could land on a "Hmm... it's not here" page, because the page was still using the temporary ID assigned before saving instead of the real one.

This change resolves that temporary ID to the real one once the server responds, so the page keeps showing the agent instead of erroring out — across all the agent screens (edit, name, prompt, avatar), not just the main one. The temporary-to-real mapping is now saved to disk (not just kept in memory) so this also works after a refresh, and old entries are automatically cleaned up over time so it doesn't grow unbounded.

Fixed Issues

$ #93272
PROPOSAL: #93272 comment

Tests

Precondition:

  • The internet connection speed must be set to 3G.
  1. Go to Account > Agents.
  2. Click New agent.
  3. Create a new agent and save it.
  4. As soon as the new agent appears in the list, open the agent edit page before creation fully finishes.
  5. Wait for CreateAgent to finish.
  6. Verify the edit page continues showing the created agent.
  7. Verify the “Hmm... it’s not here” page does not appear.
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

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

Screenshots/Videos

Android: Native
Screen.Recording.2026-07-20.at.11.40.58.PM.mov
Android: mWeb Chrome
Screen_Recording_20260720_235440_Chrome.mp4
iOS: Native
Screen.Recording.2026-07-20.at.11.59.49.PM.mov
iOS: mWeb Safari
Screen.Recording.2026-07-21.at.12.00.44.AM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-07-29.at.10.28.37.AM.mov

@melvin-bot
melvin-bot Bot requested review from marufsharifi and trjExpensify and removed request for a team July 20, 2026 13:10
@melvin-bot

melvin-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

@marufsharifi 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 July 20, 2026 13:10

@trjExpensify trjExpensify left a comment

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.

I can't reproduce this bug on staging.

2026-07-20_16-48-07.mp4

@marufsharifi

Copy link
Copy Markdown

@trjExpensify, this issue is reproducible only under a slow network connection.

@nabi-ebrahimi, could you please update the checklist to include the following precondition?

The internet connection speed must be set to 3G.

@marufsharifi

marufsharifi commented Jul 21, 2026 •

Copy link
Copy Markdown

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 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
    • 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
    • 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 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
Screen.Recording.2026-07-21.at.11.27.40.AM.mov
Android: mWeb Chrome
Screen_Recording_20260721_113936_Chrome.mp4
iOS: HybridApp
Screen.Recording.2026-07-21.at.12.20.55.PM.mov
iOS: mWeb Safari
Screen.Recording.2026-07-21.at.11.51.14.AM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-07-29.at.10.36.23.AM.mov

trjExpensify
trjExpensify previously approved these changes Jul 22, 2026

@trjExpensify trjExpensify left a comment

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.

Thanks for updating the tests. 👍

@marufsharifi

Copy link
Copy Markdown

@nabi-ebrahimi, could you please check this. thanks.

RF-1.mp4

@nabi-ebrahimi

Copy link
Copy Markdown
Author

@nabi-ebrahimi, could you please check this. thanks.

RF-1.mp4

@marufsharifi thanks for the review.

The current approach in this PR is to disable the pending row until the data comes back from the server, so the user cannot open the agent too early and land on the "Hmm... it's not here" page.

In the offline case, the newly created agent is still only a pending optimistic row and does not have a stable server identity yet, so the row stays unavailable until creation completes.

What do you think about keeping this behavior for offline-created agents? Or would you prefer a different UX here?

cc @trjExpensify

@marufsharifi

Copy link
Copy Markdown

@trjExpensify, could you please check this when you get a chance. thanks.

@trjExpensify

Copy link
Copy Markdown
Contributor

I think how it works offline today (shown below) is what we should be aiming for, but @puneetlath @NicolasBonet might have an opinion as well.

2026-07-20_16-48-07.mp4

@NicolasBonet

Copy link
Copy Markdown
Contributor

I think how it works offline today (shown below) is what we should be aiming for, but @puneetlath @NicolasBonet might have an opinion as well.

I think this makes sense, yes

@marufsharifi

Copy link
Copy Markdown

@nabi-ebrahimi, is it clear how it should be? thanks.

@nabi-ebrahimi

Copy link
Copy Markdown
Author

@nabi-ebrahimi, could you please check this. thanks.

RF-1.mp4

@marufsharifi, I've addressed the issue. Could you please review it again? Thanks!

@nabi-ebrahimi

Copy link
Copy Markdown
Author

I found a pre-existing bug here on the Account > Agents page while testing this PR:

Steps

  1. Go offline.
  2. Go to Account > Agents.
  3. Create a new agent with New agent.
  4. Open that newly created agent
  5. Change the agent name or avatar.
  6. Go back online.

Expected

The offline-created agent should sync as a single agent row, including the name/avatar change made before reconnecting.

Actual

After reconnecting, a duplicate/stale agent row can appear. The row cannot be edited/deleted properly.

Root cause

Offline agent creation is represented locally with an optimistic personal detail and an optimistic accountID.

The name/avatar edit flows currently treat that optimistic row like a fully synced agent, so they enqueue UpdateAgentName / UpdateAgentAvatar separately with the temporary optimistic accountID. Once CreateAgent resolves and the real server agent is created, that temporary ID is no longer the correct target for the queued update, which can leave stale local data after reconnect.

Screenshots/Videos

Screen.Recording.2026-07-23.at.10.58.30.AM.mp4

Do you think we should fix this in this PR or handle it separately?

Thanks!

CC @trjExpensify, @marufsharifi

@marufsharifi marufsharifi 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.

🔴 The title/description don't match the implementation.

  • Title: "Disable edit navigation for pending agents"
  • Body: "…temporarily non-interactive… Verify the Edit button is disabled while creation is still pending."

But the diff does neither — it does accountID remapping, and AgentsPage.tsx:112 still navigates unconditionally:

action: () => Navigation.navigate(ROUTES.SETTINGS_AGENTS_EDIT.getRoute(accountID)),

Test steps 4–6 (row "temporarily unavailable", Edit button disabled while pending) can't pass against this diff because that gating code doesn't exist. Please either (a) rewrite the description to document the ID-remapping approach, or (b) if you pivoted to the "disable while pending" approach, add that logic to AgentsPage/EditAgentPage. This PR currently looks like it's missing the files that implement the described behavior.

Comment thread src/ONYXKEYS.ts Resolved
Comment thread src/libs/ExportOnyxState/common.ts Resolved
Comment thread src/libs/ExportOnyxState/common.ts Resolved
@codecov

codecov Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/ONYXKEYS.ts 100.00% <ø> (ø)
src/hooks/useResolvedAgentAccountID.ts 100.00% <100.00%> (ø)
src/libs/ExportOnyxState/common.ts 80.95% <ø> (ø)
src/libs/actions/Agent.ts 71.42% <100.00%> (+0.97%) ⬆️
...bs/actions/pruneOptimisticAgentAccountIDMapping.ts 100.00% <100.00%> (ø)
src/pages/settings/Agents/EditAgentPage.tsx 91.30% <100.00%> (ø)
src/pages/settings/Agents/Fields/EditNamePage.tsx 95.45% <100.00%> (ø)
...rc/pages/settings/Agents/Fields/EditPromptPage.tsx 71.42% <100.00%> (ø)
...ges/settings/Agents/Fields/EditAgentAvatarPage.tsx 0.00% <0.00%> (ø)
... and 7 files with indirect coverage changes

@nabi-ebrahimi

Copy link
Copy Markdown
Author

@marufsharifi, I've addressed your comment. Could you check it once again?

@marufsharifi

Copy link
Copy Markdown

@nabi-ebrahimi, could you please merge the main.

Comment thread src/hooks/useResolvedAgentAccountID.ts Resolved
Comment thread src/libs/actions/Agent.ts Resolved
const [agentPrompts] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_AGENT_PROMPT);
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
// Read here (rather than inside openAgentsPage()) so the action stays a pure function of its params.
const [optimisticAgentAccountIDMappingCreatedAt] = useOnyx(ONYXKEYS.OPTIMISTIC_AGENT_ACCOUNT_ID_MAPPING_CREATED_AT);

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.

Let's do that @nabi-ebrahimi

@github-actions

Copy link
Copy Markdown
Contributor

This PR adds a new Onyx.connectWithoutView call, so I've requested a review from the Onyx performance reviewers (@tgolen, @mountiny, @luacmartins) — a review from any one of them is enough. Please add a link in your PR description to the Slack discussion where the @frontend-performance team approved using connectWithoutView here.

Comment thread src/libs/actions/Agent.ts Resolved
Comment thread src/libs/actions/Agent.ts Resolved
Comment thread src/libs/actions/Agent.ts Resolved
@nabi-ebrahimi

Copy link
Copy Markdown
Author

@tgolen, addressed your feedback. Could you take another look? Thanks

@tgolen tgolen left a comment

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.

Thanks for the changes! I think we're getting there, but I'm still not fully understanding how the data can go stale.

Comment on lines +18 to +20
* The one caller that does still trigger it explicitly is openAgentsPage() (Agent.ts): an entry can go stale
* purely from time passing with no new write to fire this callback, and opening the agents list is the only
* realistic moment to re-check for that.

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 still feels like it's somewhat in the same boat as previously (where it is triggered by something in the UI). How come the entries can go stale from time passing? Maybe the comment can be improved to explain why that happens.

If the time component is real for them going stale, wouldn't it be better to use an interval timer to re-run this check on a regular basis, rather than wait for a UI to trigger it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks. Improved the comment.

On the interval timer — checked the codebase for precedent first: checkForUpdates.ts is the only periodic-check pattern here, and it's deliberately a no-op on native (platformSetup/index.native.ts), so a raw interval isn't something this app relies on for mobile.

Instead, added AppStateMonitor.addBecameActiveListener() alongside openAgentsPage() — it re-checks whenever the app resumes from background, which is the moment the most time is likely to have silently passed, using a pattern already proven on mobile elsewhere in this codebase.

@mountiny
mountiny removed their request for review August 31, 2026 09:55
Comment thread src/hooks/useResolvedAgentAccountID.ts Resolved
Comment thread src/hooks/useResolvedAgentAccountID.ts Resolved
@github-actions
github-actions Bot requested a review from mountiny September 1, 2026 04:35
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR adds a new Onyx.connectWithoutView call, so I've requested a review from the Onyx performance reviewers (@tgolen, @mountiny, @luacmartins) — a review from any one of them is enough. Please add a link in your PR description to the Slack discussion where the @frontend-performance team approved using connectWithoutView here.

@nabi-ebrahimi

Copy link
Copy Markdown
Author

@marufsharifi, I’ve addressed your latest feedback. Could you take another look?

@tgolen tgolen changed the title Resolve optimistic agent edit route after creation [HOLD] Resolve optimistic agent edit route after creation Sep 1, 2026
@tgolen

tgolen commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I've had some concerns with the solution in this PR and was discussing some alternatives with @NicolasBonet. I'm going to place a HOLD on this PR until we able to resolve it.

@NicolasBonet

Copy link
Copy Markdown
Contributor

I believe we can close this one in favor of #100068

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.

8 participants