You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-10-23] [$175] Add an expiry date/time to vacation delegates so delegation ends automatically #102202
When a vacation delegate is set — by the user or by an admin — nothing in NewDot tells the original approver that their reports are being routed elsewhere. The only place the delegate appears is under Profile > Status, which most people never open.
Two failure modes come out of this:
Users forget to remove the delegate when they get back, so reports keep routing to the delegate indefinitely.
When an admin sets the delegate on someone's behalf, the approver returns from vacation, stops receiving reports to approve, and has no obvious indication of why.
Classic surfaces a banner for this. NewDot has no equivalent — not on Home, not in Account settings.
Proposal
Pull Vacation Delegate out of Status and directly into Profile for greater visibility of this feature.
Rather than only adding a passive warning, make the delegation self-expiring:
Add a date/time picker to the vacation delegate flow so the person setting it (user or admin) chooses when the delegation starts and ends.
Automatically clear the vacation delegate when the end date/time passes, restoring the original approver in the workflow.
Reflect the active date range wherever the delegate is shown, so both the vacationer and the admin can see when it expires.
This matches how people already think about being out of office and removes the manual cleanup step that's being missed today.
Mockups
Vacation Delegate moves to the main Profile page:
When clicked and no delegate is set open directly on the member selection screen:
The "clear after" option is not mandatory but is recommended:
changed the title [-]Add an expiry date/time to vacation delegates so delegation ends automatically[/-][+][$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/+]on Sep 25, 2026
Vacation delegation is currently treated as an indefinite profile setting. The existing flow lets a user or admin choose a delegate, but it does not store or enforce a start/end time for that delegation. Because of that, NewDot has no way to automatically restore the original approver, and the active delegate is easy to miss because it is only shown under Profile > Status.
What changes do you think we should make in order to solve the problem?
Move Vacation Delegate from the Status page to the main Profile page, while keeping the same underlying delegation setting.
Extend the vacation delegate flow with optional start and end date/time values. The selected delegate and active date range should be shown wherever the vacation delegate is displayed.
Treat a delegate as active only while the current time is within that configured range. Once the end date/time passes, the original approver should be used again automatically instead of requiring the user to manually clear the delegate.
Reuse the same behavior for both self-service and admin-managed delegation so they cannot drift apart.
Add focused tests for: no expiry, scheduled delegation, active delegation, expired delegation, and the profile display of the configured date range.
What alternative solutions did you explore? (Optional)
A banner/reminder alone would improve visibility but would still leave cleanup dependent on the user. Moving the setting to Profile without adding expiry would have the same limitation. Making the delegation time-bounded solves the underlying problem while the Profile placement makes the state easier to discover.
📣 @Siwanadfour! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
I verified the backend side on production (dev build through USE_WEB_PROXY) rather than inferring it:
The server drops a date today. I sent SetVacationDelegate with an extra clearAfter=2026-10-01. It returned 200, but the server's own update set nvp_private_vacationDelegate to {creator, delegate} only. So a client-only change would show a date that no other device and no approval routing ever sees. The backend has to store the date.
The server already knows how to clear something at a date. Custom status works this way. I set a status with clearAfter two minutes ahead and UpdateStatus stored a jobID. Then I closed the browser. 37 s after the expiry, OpenApp returned status: null. The server cleared it with no client open.
That second point decides where expiry must live. The client also has a status timer:
That timer can't expire a delegate. The vacationer is away with the app closed, and when an admin sets the delegate, it lives on another person's account. Both failure modes in the issue are exactly when no client is running.
What changes do you think we should make in order to solve the problem?
1. Backend (internal).SetVacationDelegate accepts an optional clearAfter (yyyy-MM-dd, end of that day in the vacationer's timezone). The server stores it on the NVP and schedules the same kind of job UpdateStatus uses. When the job runs, it deletes the delegate, pushes null to the vacationer and the domain record, and clears the delegate's delegatorFor. Routing should also ignore a delegate whose clearAfter has passed, in case the job runs late.
2. App. Add clearAfter to BaseVacationDelegate and to the params. Send it from the self-service action and from the domain action, because both use the same command:
A Vacation delegate row on Profile, after Status. With no delegate it opens the member picker directly. Otherwise it shows the delegate and Until Oct 1, 2026. The error dot moves here from Status.
A small VacationDelegateForm: the delegate row, a DatePicker labelled Clear after (recommended) with minDate today, the note Vacation delegate will clear on Oct 1, 2026, and Save. The date is optional.
The same form on the domain member page, so an admin sets the date too. VacationDelegateMenuItem gets the Until … line, so the admin sees it as well.
The section is removed from Status. Changing the delegate, including through the missing-workspaces step, keeps the existing clearAfter.
With the change, the same steps show the new flow on production. Profile → Vacation delegate opens the picker. I picked a member and pressed Skip on the missing-workspaces step, which landed on the form. I chose Oct 1 and saved. The request carried clearAfter=2026-10-01 and the row read Until Oct 1, 2026. Stashing the patch brings back the old Profile with no row.
[[ATTACH before-status.png]]
[[ATTACH after-form-date.png]]
[[ATTACH after-profile.png]]
One limitation: until the backend part ships, the date only lives in the client's optimistic data. Nothing expires, and other devices don't see it.
Tests: the action sends clearAfter and clears it on delete. The Profile row routes to the picker only when there is no delegate and no delegatorFor. The form keeps Save enabled with no date.
What alternative solutions did you explore? (Optional)
Clear it from the client with a timer, like UserStatusHandler. It only fires while the vacationer's own app is open. For an admin-set delegate, or a vacationer who is offline, reports keep routing to the delegate, which is the bug in the issue. The server already clears statuses on its own (measured above), so it can do the same here.
Move the row to Profile and add a banner, with no expiry. It fixes visibility, but cleanup still depends on someone remembering. The issue asks for automatic clearing.
A start date too. The mockups only show "Clear after", so I left it out. It could be added to the same form later.
changed the title [-][$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/-][+][Due for payment 2026-10-23] [$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/+]on Oct 16, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.12-1 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-10-23. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
[Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment: N/A - Result of the original design
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue:
Regression Test Proposal
Prerequisites
Account A is an admin of workspace W. Accounts B and C are members of W.
Account D is not a member of W.
A domain admin account, and a domain member M.
In the steps below, "tomorrow" stands for tomorrow's date. The date input shows it as yyyy-MM-dd (e.g. 2026-10-13). The note under the inputs and the "Until" label show it as MMM d, yyyy followed by the time (e.g. Oct 13, 2026 11:59 PM).
Profile row and Status page
Sign in as Account A and go to Account > Profile. Verify a Vacation delegate row shows right after Status, with no value.
Open Status. Verify there's no vacation delegate section at the bottom of the page. Go back.
Set a delegate with a clear after date and time
Click Vacation delegate. Verify a form opens with:
the header Vacation delegate;
the text "Set a vacation delegate to approve reports on your behalf while you're out of office.";
an unselected Choose delegate row;
an empty Clear after (recommended) input with a calendar icon;
a disabled Save button.
Verify there's no Time row, no "Vacation delegate will clear on …" note, and no Remove vacation delegate button.
Click Choose delegate. Verify the member picker opens with the header Choose delegate, with no member preselected. Click its back button and verify you return to the form.
Click Choose delegate again and pick Account B. Verify you return to the form, which now shows Account B's avatar and name as a pill under Choose delegate, with Save enabled.
Click the Clear after (recommended) input. Verify the calendar opens and days before today can't be picked. Pick tomorrow. Verify:
the input shows tomorrow as yyyy-MM-dd (e.g. 2026-10-13);
the calendar icon is replaced by a clear (X) button in the same spot;
a Time row appears, showing 11:59 PM;
the note reads "Vacation delegate will clear on Oct 13, 2026 11:59 PM".
Click the X. Verify the input is empty again with the calendar icon in the same spot, and the Time row and the note are gone. Pick tomorrow again.
Click the Time row, set 5:30 PM and confirm. Verify the Time row shows 5:30 PM and the note reads "Vacation delegate will clear on Oct 13, 2026 5:30 PM".
Click Save. Verify:
Save shows a spinner;
Remove vacation delegate never appears, either while saving or while the RHP closes;
the RHP closes;
the Profile row shows the Vacation delegate label, Account B's pill, and "Until Oct 13, 2026 5:30 PM" under it.
Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".
Edit and remove
Click the Vacation delegate row. Verify the form opens showing:
Account B under Choose delegate;
2026-10-13 in Clear after (recommended), with the X;
5:30 PM in Time;
the "Vacation delegate will clear on Oct 13, 2026 5:30 PM" note;
a Remove vacation delegate button.
Change only the Time to 6:00 PM and click Save. Verify the RHP closes right away (no spinner) and the Profile row shows Account B with "Until Oct 13, 2026 6:00 PM".
Open the form and click Choose delegate. Verify the picker shows Account B selected at the top. Pick Account C. Verify you return to the form with Account C's pill, and the date, time and note are unchanged.
Click Save. Verify Save shows a spinner, then the RHP closes and the Profile row shows Account C's pill with "Until Oct 13, 2026 6:00 PM".
Open the form. Pick today's date, then set Time to a time that has already passed. Click Save. Verify:
"Please choose a time at least one minute ahead" shows under Time;
the form stays open;
no request is sent.
Click the header back button without saving. Verify you return to Profile. Click the Vacation delegate row again and verify the form shows the saved values (Account C, 2026-10-13, 6:00 PM), not the unsaved ones.
Click the X on Clear after (recommended) and click Save. Verify the RHP closes and the Profile row shows Account C with no "Until" line. Refresh and verify it stays that way.
Open the form and click Remove vacation delegate. Verify the RHP closes and the Profile row shows Vacation delegate with no value.
No date
Set Account B as delegate without picking a date and click Save. Verify the Profile row shows Account B's pill with no "Until" line. Open the form again and verify Clear after (recommended) is empty, with the calendar icon and no Time row.
Error from the backend
Have Account C set its own vacation delegate. Then, as Account A, open the form, pick Account C and click Save. Verify:
an Add vacation delegate modal shows the backend's error message;
no error row with an X shows under the delegate pill.
Click Got it. Verify the modal closes, the spinner on Save stops, the form shows the previous delegate again, and Save can be clicked again.
Automatic clearing
Set Account B as delegate with today's date and a time 2 minutes ahead, then click Save. Verify the Profile row shows "Until ". Wait until that time passes and refresh. Verify:
the Profile row shows no delegate;
clicking it opens the form with Choose delegate unselected;
clicking Choose delegate shows no member preselected.
Missing workspaces step
Note
This step only appears when Account A belongs to at least one workspace that the chosen delegate is not a member of. With no workspaces, or a delegate who is already in all of them, the delegate is saved directly.
Open the form, pick Account D, pick tomorrow, set Time to 5:30 PM, and click Save. Verify:
the Profile row doesn't change while the request runs;
a screen opens saying Account D is not a member of the workspaces listed, with W under "You are an admin of these workspaces:".
Click Invite. Verify the whole RHP closes and the Profile row shows Account D's pill with "Until Oct 13, 2026 5:30 PM", appearing once without flashing.
Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.
While you're someone else's delegate
Set Account A as Account B's vacation delegate, then sign in as Account A. Click the Vacation delegate row. Verify it shows "You can't set a vacation delegate because you're currently the delegate for the following members:" with Account B listed, and no form.
Open settings/profile/vacation-delegate directly. Verify the same message shows.
Domain admin
As a domain admin, go to Domains > [domain] > Members and open your own member entry. Click Vacation delegate. Verify the form opens with Choose delegate unselected. Click Choose delegate and verify the picker header reads Choose delegate and you aren't listed (search for your own email too). Go back to the member list.
Open member M and click Vacation delegate. Verify the form opens with Choose delegate unselected. Click Choose delegate and verify the picker lists you, but not M.
Pick Account B. Verify you return to the form, which shows Account B's pill under Choose delegate. Pick tomorrow, set Time to 5:30 PM and click Save. Verify you return to M's member details, and the row shows Account B's pill with "Until Oct 13, 2026 5:30 PM". The date and time are read in M's Profile timezone.
Sign in as M and open Account > Profile. Verify the row shows Account B with the same "Until Oct 13, 2026 5:30 PM".
Back as the admin, open M's Vacation delegate row. Verify the form shows the saved date (with the X), the time and Remove vacation delegate. Click it and verify M's row shows no delegate.
Other date inputs (shared DatePicker change)
Go to Account > Profile and click Date of birth in the Private section. Note where the calendar icon sits on the date input. Pick a date. Verify the X sits exactly where the calendar icon was. Click the X and verify the field clears and the calendar icon comes back in the same spot. Pick a date again, save, and verify the field works as before.
Start a manual expense and open its Date. Pick a date and clear it. Verify the X sits exactly where the calendar icon was, and the field works as before.
Offline tests
Note
Go offline with the browser's network throttling (DevTools > Network > Offline) or by turning off the device's network. The Force offline switch under Troubleshoot still sends requests to the server, so the server applies changes that should be waiting in the queue.
Self-service (Profile)
While online, set Account B as your vacation delegate, clearing tomorrow at 5:30 PM. Then go offline.
Go to Account > Profile. Verify the row shows Account B with "Until Oct 13, 2026 5:30 PM". Click it and verify the form opens with Account B, 2026-10-13 and 5:30 PM.
Set Time to 6:00 PM. Verify the note reads "Vacation delegate will clear on Oct 13, 2026 6:00 PM". Click Save. Verify the RHP closes right away with no error, and the Profile row shows Account B with "Until Oct 13, 2026 6:00 PM", greyed out.
Click the row again. Verify the form opens with the Choose delegate pill, the date, the Time row and the note all greyed out. Go back.
Go back online. Verify the Profile row is no longer greyed out and still shows "Until Oct 13, 2026 6:00 PM", with no error.
Go offline again and open the form. Click Choose delegate. Verify the offline blocking view shows, and its back button returns to the form.
Set Time to 7:00 PM and click Save. Open the form again and click Remove vacation delegate. Verify the RHP closes and the Profile row is struck through.
Go back online. Verify the Profile row goes straight to showing no delegate, with no flash of Account B's pill or the 7:00 PM "Until" label, and no error. In the network tab, verify only DeleteVacationDelegate is sent, not the queued SetVacationDelegate.
While offline with no delegate set, click Vacation delegate. Verify the form opens with Choose delegate unselected and Save disabled. Click Choose delegate and verify the offline blocking view shows (the member picker is online only, same as on main).
Domain admin
While online, set a vacation delegate for member M, clearing tomorrow at 5:30 PM. Go offline, open M and click Vacation delegate. Verify the form opens with the saved date and time.
Set Time to 6:00 PM and click Save. Verify you return to M's details right away, and the row shows "Until Oct 13, 2026 6:00 PM", greyed out. Go back online and verify the row is no longer greyed out, with no error.
Go offline again, open the form and click Choose delegate. Verify the offline blocking view shows.
Go back to the form and click Remove vacation delegate. Verify M's row shows no delegate. Go back online and verify it stays removed, with no error.
Proposed amount: $525 - (175*3 as per docs: the total price you are requesting, not the difference. Standard increases are 50%, 2x, 3x, 4x etc. of the job price, based on the current standard job amount.)
Why the work exceeded the issue:
52 files touched
extensive testing
140 comments on PR thread (review / design / engineering back & forth)
took 3 weeks of work from PR open to merge
Regressions caused by this PR: None (so far)
Optional
Scope expansion: ~
Files touched: 52
Core flow changes
Prerequisites
Account A is an admin of workspace W. Accounts B and C are members of W.
Account D is not a member of W.
A domain admin account, and a domain member M.
In the steps below, "tomorrow" stands for tomorrow's date. The date input shows it as yyyy-MM-dd (e.g. 2026-10-13). The note under the inputs and the "Until" label show it as MMM d, yyyy followed by the time (e.g. Oct 13, 2026 11:59 PM).
Profile row and Status page
Sign in as Account A and go to Account > Profile. Verify a Vacation delegate row shows right after Status, with no value.
Open Status. Verify there's no vacation delegate section at the bottom of the page. Go back.
Set a delegate with a clear after date and time
Click Vacation delegate. Verify a form opens with:
the header Vacation delegate;
the text "Set a vacation delegate to approve reports on your behalf while you're out of office.";
an unselected Choose delegate row;
an empty Clear after (recommended) input with a calendar icon;
a disabled Save button.
Verify there's no Time row, no "Vacation delegate will clear on …" note, and no Remove vacation delegate button.
Click Choose delegate. Verify the member picker opens with the header Choose delegate, with no member preselected. Click its back button and verify you return to the form.
Click Choose delegate again and pick Account B. Verify you return to the form, which now shows Account B's avatar and name as a pill under Choose delegate, with Save enabled.
Click the Clear after (recommended) input. Verify the calendar opens and days before today can't be picked. Pick tomorrow. Verify:
the input shows tomorrow as yyyy-MM-dd (e.g. 2026-10-13);
the calendar icon is replaced by a clear (X) button in the same spot;
a Time row appears, showing 11:59 PM;
the note reads "Vacation delegate will clear on Oct 13, 2026 11:59 PM".
Click the X. Verify the input is empty again with the calendar icon in the same spot, and the Time row and the note are gone. Pick tomorrow again.
Click the Time row, set 5:30 PM and confirm. Verify the Time row shows 5:30 PM and the note reads "Vacation delegate will clear on Oct 13, 2026 5:30 PM".
Click Save. Verify:
Save shows a spinner;
Remove vacation delegate never appears, either while saving or while the RHP closes;
the RHP closes;
the Profile row shows the Vacation delegate label, Account B's pill, and "Until Oct 13, 2026 5:30 PM" under it.
Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".
Edit and remove
Click the Vacation delegate row. Verify the form opens showing:
Account B under Choose delegate;
2026-10-13 in Clear after (recommended), with the X;
5:30 PM in Time;
the "Vacation delegate will clear on Oct 13, 2026 5:30 PM" note;
a Remove vacation delegate button.
Change only the Time to 6:00 PM and click Save. Verify the RHP closes right away (no spinner) and the Profile row shows Account B with "Until Oct 13, 2026 6:00 PM".
Open the form and click Choose delegate. Verify the picker shows Account B selected at the top. Pick Account C. Verify you return to the form with Account C's pill, and the date, time and note are unchanged.
Click Save. Verify Save shows a spinner, then the RHP closes and the Profile row shows Account C's pill with "Until Oct 13, 2026 6:00 PM".
Open the form. Pick today's date, then set Time to a time that has already passed. Click Save. Verify:
"Please choose a time at least one minute ahead" shows under Time;
the form stays open;
no request is sent.
Click the header back button without saving. Verify you return to Profile. Click the Vacation delegate row again and verify the form shows the saved values (Account C, 2026-10-13, 6:00 PM), not the unsaved ones.
Click the X on Clear after (recommended) and click Save. Verify the RHP closes and the Profile row shows Account C with no "Until" line. Refresh and verify it stays that way.
Open the form and click Remove vacation delegate. Verify the RHP closes and the Profile row shows Vacation delegate with no value.
No date
Set Account B as delegate without picking a date and click Save. Verify the Profile row shows Account B's pill with no "Until" line. Open the form again and verify Clear after (recommended) is empty, with the calendar icon and no Time row.
Error from the backend
Have Account C set its own vacation delegate. Then, as Account A, open the form, pick Account C and click Save. Verify:
an Add vacation delegate modal shows the backend's error message;
no error row with an X shows under the delegate pill.
Click Got it. Verify the modal closes, the spinner on Save stops, the form shows the previous delegate again, and Save can be clicked again.
Automatic clearing
Set Account B as delegate with today's date and a time 2 minutes ahead, then click Save. Verify the Profile row shows "Until ". Wait until that time passes and refresh. Verify:
the Profile row shows no delegate;
clicking it opens the form with Choose delegate unselected;
clicking Choose delegate shows no member preselected.
Missing workspaces step
[!NOTE]
This step only appears when Account A belongs to at least one workspace that the chosen delegate is not a member of. With no workspaces, or a delegate who is already in all of them, the delegate is saved directly.
Open the form, pick Account D, pick tomorrow, set Time to 5:30 PM, and click Save. Verify:
the Profile row doesn't change while the request runs;
a screen opens saying Account D is not a member of the workspaces listed, with W under "You are an admin of these workspaces:".
Click Invite. Verify the whole RHP closes and the Profile row shows Account D's pill with "Until Oct 13, 2026 5:30 PM", appearing once without flashing.
Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.
While you're someone else's delegate
Set Account A as Account B's vacation delegate, then sign in as Account A. Click the Vacation delegate row. Verify it shows "You can't set a vacation delegate because you're currently the delegate for the following members:" with Account B listed, and no form.
Open settings/profile/vacation-delegate directly. Verify the same message shows.
Domain admin
As a domain admin, go to Domains > [domain] > Members and open your own member entry. Click Vacation delegate. Verify the form opens with Choose delegate unselected. Click Choose delegate and verify the picker header reads Choose delegate and you aren't listed (search for your own email too). Go back to the member list.
Open member M and click Vacation delegate. Verify the form opens with Choose delegate unselected. Click Choose delegate and verify the picker lists you, but not M.
Pick Account B. Verify you return to the form, which shows Account B's pill under Choose delegate. Pick tomorrow, set Time to 5:30 PM and click Save. Verify you return to M's member details, and the row shows Account B's pill with "Until Oct 13, 2026 5:30 PM". The date and time are read in M's Profile timezone.
Sign in as M and open Account > Profile. Verify the row shows Account B with the same "Until Oct 13, 2026 5:30 PM".
Back as the admin, open M's Vacation delegate row. Verify the form shows the saved date (with the X), the time and Remove vacation delegate. Click it and verify M's row shows no delegate.
Other date inputs (shared DatePicker change)
Go to Account > Profile and click Date of birth in the Private section. Note where the calendar icon sits on the date input. Pick a date. Verify the X sits exactly where the calendar icon was. Click the X and verify the field clears and the calendar icon comes back in the same spot. Pick a date again, save, and verify the field works as before.
Start a manual expense and open its Date. Pick a date and clear it. Verify the X sits exactly where the calendar icon was, and the field works as before.
Offline tests
[!NOTE]
Go offline with the browser's network throttling (DevTools > Network > Offline) or by turning off the device's network. The Force offline switch under Troubleshoot still sends requests to the server, so the server applies changes that should be waiting in the queue.
Self-service (Profile)
While online, set Account B as your vacation delegate, clearing tomorrow at 5:30 PM. Then go offline.
Go to Account > Profile. Verify the row shows Account B with "Until Oct 13, 2026 5:30 PM". Click it and verify the form opens with Account B, 2026-10-13 and 5:30 PM.
Set Time to 6:00 PM. Verify the note reads "Vacation delegate will clear on Oct 13, 2026 6:00 PM". Click Save. Verify the RHP closes right away with no error, and the Profile row shows Account B with "Until Oct 13, 2026 6:00 PM", greyed out.
Click the row again. Verify the form opens with the Choose delegate pill, the date, the Time row and the note all greyed out. Go back.
Go back online. Verify the Profile row is no longer greyed out and still shows "Until Oct 13, 2026 6:00 PM", with no error.
Go offline again and open the form. Click Choose delegate. Verify the offline blocking view shows, and its back button returns to the form.
Set Time to 7:00 PM and click Save. Open the form again and click Remove vacation delegate. Verify the RHP closes and the Profile row is struck through.
Go back online. Verify the Profile row goes straight to showing no delegate, with no flash of Account B's pill or the 7:00 PM "Until" label, and no error. In the network tab, verify only DeleteVacationDelegate is sent, not the queued SetVacationDelegate.
While offline with no delegate set, click Vacation delegate. Verify the form opens with Choose delegate unselected and Save disabled. Click Choose delegate and verify the offline blocking view shows (the member picker is online only, same as on main).
Domain admin
While online, set a vacation delegate for member M, clearing tomorrow at 5:30 PM. Go offline, open M and click Vacation delegate. Verify the form opens with the saved date and time.
Set Time to 6:00 PM and click Save. Verify you return to M's details right away, and the row shows "Until Oct 13, 2026 6:00 PM", greyed out. Go back online and verify the row is no longer greyed out, with no error.
Go offline again, open the form and click Choose delegate. Verify the offline blocking view shows.
Go back to the form and click Remove vacation delegate. Verify M's row shows no delegate. Go back online and verify it stays removed, with no error.
Activity
melvin-bot commentedon Sep 25, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (
External)[-]Add an expiry date/time to vacation delegates so delegation ends automatically[/-][+][$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/+]melvin-bot commentedon Sep 25, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022103440766935281039
Siwanadfour commentedon Sep 25, 2026
Proposal
What is the root cause of that problem?
Vacation delegation is currently treated as an indefinite profile setting. The existing flow lets a user or admin choose a delegate, but it does not store or enforce a start/end time for that delegation. Because of that, NewDot has no way to automatically restore the original approver, and the active delegate is easy to miss because it is only shown under Profile > Status.
What changes do you think we should make in order to solve the problem?
What alternative solutions did you explore? (Optional)
A banner/reminder alone would improve visibility but would still leave cleanup dependent on the user. Moving the setting to Profile without adding expiry would have the same limitation. Making the delegation time-bounded solves the underlying problem while the Profile placement makes the state easier to discover.
melvin-bot commentedon Sep 25, 2026
📣 @Siwanadfour! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
yusufdeveloper2903 commentedon Sep 25, 2026
Proposal
What is the root cause of that problem?
A vacation delegate has no end date anywhere in the chain, so nothing can expire it. The API takes only who and by whom:
App/src/libs/API/parameters/SetVacationDelegateParams.ts
Lines 1 to 7 in 3549627
the NVP type has no date field:
App/src/types/onyx/VacationDelegate.ts
Lines 13 to 22 in 3549627
and the only place the delegate appears is a section at the bottom of the Status page:
App/src/pages/settings/Profile/CustomStatus/StatusPage.tsx
Lines 247 to 266 in 3549627
The Profile row for Status only borrows the delegate's error dot. It never shows the delegate:
App/src/pages/settings/Profile/ProfilePage.tsx
Lines 127 to 134 in 3549627
I verified the backend side on production (dev build through
USE_WEB_PROXY) rather than inferring it:SetVacationDelegatewith an extraclearAfter=2026-10-01. It returned 200, but the server's own update setnvp_private_vacationDelegateto{creator, delegate}only. So a client-only change would show a date that no other device and no approval routing ever sees. The backend has to store the date.clearAftertwo minutes ahead andUpdateStatusstored ajobID. Then I closed the browser. 37 s after the expiry,OpenAppreturnedstatus: null. The server cleared it with no client open.That second point decides where expiry must live. The client also has a status timer:
App/src/libs/Navigation/AppNavigator/UserStatusHandler.tsx
Lines 27 to 36 in 3549627
That timer can't expire a delegate. The vacationer is away with the app closed, and when an admin sets the delegate, it lives on another person's account. Both failure modes in the issue are exactly when no client is running.
What changes do you think we should make in order to solve the problem?
1. Backend (internal).
SetVacationDelegateaccepts an optionalclearAfter(yyyy-MM-dd, end of that day in the vacationer's timezone). The server stores it on the NVP and schedules the same kind of jobUpdateStatususes. When the job runs, it deletes the delegate, pushesnullto the vacationer and the domain record, and clears the delegate'sdelegatorFor. Routing should also ignore a delegate whoseclearAfterhas passed, in case the job runs late.2. App. Add
clearAftertoBaseVacationDelegateand to the params. Send it from the self-service action and from the domain action, because both use the same command:App/src/libs/actions/VacationDelegate.ts
Lines 76 to 80 in 3549627
App/src/libs/actions/Domain.ts
Lines 1759 to 1765 in 3549627
vacationDelegateEmail: delegate, + clearAfter, overridePolicyDiffWarning: shouldOverridePolicyDiffWarning,3. UI, per the mockups.
Until Oct 1, 2026. The error dot moves here from Status.VacationDelegateForm: the delegate row, aDatePickerlabelled Clear after (recommended) withminDatetoday, the note Vacation delegate will clear on Oct 1, 2026, and Save. The date is optional.VacationDelegateMenuItemgets theUntil …line, so the admin sees it as well.clearAfter.With the change, the same steps show the new flow on production. Profile → Vacation delegate opens the picker. I picked a member and pressed Skip on the missing-workspaces step, which landed on the form. I chose Oct 1 and saved. The request carried
clearAfter=2026-10-01and the row readUntil Oct 1, 2026. Stashing the patch brings back the old Profile with no row.[[ATTACH before-status.png]]
[[ATTACH after-form-date.png]]
[[ATTACH after-profile.png]]
One limitation: until the backend part ships, the date only lives in the client's optimistic data. Nothing expires, and other devices don't see it.
Tests: the action sends
clearAfterand clears it on delete. The Profile row routes to the picker only when there is no delegate and nodelegatorFor. The form keeps Save enabled with no date.Demo branch: yusufdeveloper2903/App@3549627...proposal/102202
What alternative solutions did you explore? (Optional)
UserStatusHandler. It only fires while the vacationer's own app is open. For an admin-set delegate, or a vacationer who is offline, reports keep routing to the delegate, which is the bug in the issue. The server already clears statuses on its own (measured above), so it can do the same here.Siwanadfour commentedon Sep 25, 2026
Contributor details
Your Expensify account email: [email protected]
Upwork Profile Link: https://www.upwork.com/freelancers/~015f18f27caf2b5875
melvin-bot commentedon Sep 25, 2026
28 remaining items
luacmartins commentedon Oct 8, 2026
@ikevin127 PRs are in production
[-][$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/-][+][Due for payment 2026-10-23] [$175] Add an expiry date/time to vacation delegates so delegation ends automatically[/+]melvin-bot commentedon Oct 16, 2026
@ikevin127
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.12-1 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-10-23. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
ikevin127 commentedon Oct 16, 2026
BugZero Checklist
[Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment: N/A - Result of the original design
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue:
Regression Test Proposal
Prerequisites
In the steps below, "tomorrow" stands for tomorrow's date. The date input shows it as
yyyy-MM-dd(e.g.2026-10-13). The note under the inputs and the "Until" label show it asMMM d, yyyyfollowed by the time (e.g.Oct 13, 2026 11:59 PM).Profile row and Status page
Set a delegate with a clear after date and time
Click Vacation delegate. Verify a form opens with:
Verify there's no Time row, no "Vacation delegate will clear on …" note, and no Remove vacation delegate button.
Click Choose delegate. Verify the member picker opens with the header Choose delegate, with no member preselected. Click its back button and verify you return to the form.
Click Choose delegate again and pick Account B. Verify you return to the form, which now shows Account B's avatar and name as a pill under Choose delegate, with Save enabled.
Click the Clear after (recommended) input. Verify the calendar opens and days before today can't be picked. Pick tomorrow. Verify:
yyyy-MM-dd(e.g.2026-10-13);11:59 PM;Click the X. Verify the input is empty again with the calendar icon in the same spot, and the Time row and the note are gone. Pick tomorrow again.
Click the Time row, set 5:30 PM and confirm. Verify the Time row shows
5:30 PMand the note reads "Vacation delegate will clear on Oct 13, 2026 5:30 PM".Click Save. Verify:
Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".
Edit and remove
2026-10-13in Clear after (recommended), with the X;5:30 PMin Time;2026-10-13,6:00 PM), not the unsaved ones.No date
Error from the backend
Have Account C set its own vacation delegate. Then, as Account A, open the form, pick Account C and click Save. Verify:
Click Got it. Verify the modal closes, the spinner on Save stops, the form shows the previous delegate again, and Save can be clicked again.
Automatic clearing
Missing workspaces step
Note
This step only appears when Account A belongs to at least one workspace that the chosen delegate is not a member of. With no workspaces, or a delegate who is already in all of them, the delegate is saved directly.
Open the form, pick Account D, pick tomorrow, set Time to 5:30 PM, and click Save. Verify:
Click Invite. Verify the whole RHP closes and the Profile row shows Account D's pill with "Until Oct 13, 2026 5:30 PM", appearing once without flashing.
Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.
While you're someone else's delegate
settings/profile/vacation-delegatedirectly. Verify the same message shows.Domain admin
Other date inputs (shared
DatePickerchange)Offline tests
Note
Go offline with the browser's network throttling (DevTools > Network > Offline) or by turning off the device's network. The Force offline switch under Troubleshoot still sends requests to the server, so the server applies changes that should be waiting in the queue.
Self-service (Profile)
2026-10-13and5:30 PM.DeleteVacationDelegateis sent, not the queuedSetVacationDelegate.main).Domain admin
Do we agree 👍 or 👎.
ikevin127 commentedon Oct 16, 2026
💸 Price Increase Request
Mandatory
Proposed amount: $525 - (175*3 as per docs:
the total price you are requesting, not the difference. Standard increases are 50%, 2x, 3x, 4x etc. of the job price, based on the current standard job amount.)Why the work exceeded the issue:
Regressions caused by this PR: None (so far)
Optional
Scope expansion: ~
Files touched: 52
Core flow changes
Prerequisites
In the steps below, "tomorrow" stands for tomorrow's date. The date input shows it as
yyyy-MM-dd(e.g.2026-10-13). The note under the inputs and the "Until" label show it asMMM d, yyyyfollowed by the time (e.g.Oct 13, 2026 11:59 PM).Profile row and Status page
Set a delegate with a clear after date and time
Click Vacation delegate. Verify a form opens with:
Verify there's no Time row, no "Vacation delegate will clear on …" note, and no Remove vacation delegate button.
Click Choose delegate. Verify the member picker opens with the header Choose delegate, with no member preselected. Click its back button and verify you return to the form.
Click Choose delegate again and pick Account B. Verify you return to the form, which now shows Account B's avatar and name as a pill under Choose delegate, with Save enabled.
Click the Clear after (recommended) input. Verify the calendar opens and days before today can't be picked. Pick tomorrow. Verify:
yyyy-MM-dd(e.g.2026-10-13);11:59 PM;Click the X. Verify the input is empty again with the calendar icon in the same spot, and the Time row and the note are gone. Pick tomorrow again.
Click the Time row, set 5:30 PM and confirm. Verify the Time row shows
5:30 PMand the note reads "Vacation delegate will clear on Oct 13, 2026 5:30 PM".Click Save. Verify:
Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".
Edit and remove
2026-10-13in Clear after (recommended), with the X;5:30 PMin Time;2026-10-13,6:00 PM), not the unsaved ones.No date
Error from the backend
Have Account C set its own vacation delegate. Then, as Account A, open the form, pick Account C and click Save. Verify:
Click Got it. Verify the modal closes, the spinner on Save stops, the form shows the previous delegate again, and Save can be clicked again.
Automatic clearing
Missing workspaces step
Open the form, pick Account D, pick tomorrow, set Time to 5:30 PM, and click Save. Verify:
Click Invite. Verify the whole RHP closes and the Profile row shows Account D's pill with "Until Oct 13, 2026 5:30 PM", appearing once without flashing.
Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.
While you're someone else's delegate
settings/profile/vacation-delegatedirectly. Verify the same message shows.Domain admin
Other date inputs (shared
DatePickerchange)Offline tests
Self-service (Profile)
2026-10-13and5:30 PM.DeleteVacationDelegateis sent, not the queuedSetVacationDelegate.main).Domain admin
Test coverage: #102472 (comment)
Additional fixes: ~
Deploy blockers audited: ~
Multiple PRs: One docs follow-up PR #104126.
cc @luacmartins for increase request confirmation 🙌
melvin-bot commentedon Oct 22, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)