Skip to content

[Due for payment 2026-10-23] [$175] Add an expiry date/time to vacation delegates so delegation ends automatically #102202

Open

Description

@flaviadefaria

Problem

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:

  1. Users forget to remove the delegate when they get back, so reports keep routing to the delegate indefinitely.
  2. 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

  1. Pull Vacation Delegate out of Status and directly into Profile for greater visibility of this feature.

  2. 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:
Image

When clicked and no delegate is set open directly on the member selection screen:
Image

The "clear after" option is not mandatory but is recommended:
Image

Note gets added with the clearing date:
Image

The vacation delegate is added to the profile:
Image

Reported in Slack.

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @ikevin127

Activity

  1. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 25, 2026
  2. melvin-bot commented on Sep 25, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (External)

  3. 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
  4. melvin-bot commented on Sep 25, 2026

    @melvin-bot
  5. Siwanadfour commented on Sep 25, 2026

    @Siwanadfour

    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?

    1. Move Vacation Delegate from the Status page to the main Profile page, while keeping the same underlying delegation setting.
    2. 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.
    3. 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.
    4. Reuse the same behavior for both self-service and admin-managed delegation so they cannot drift apart.
    5. 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.

  6. melvin-bot commented on Sep 25, 2026

    @melvin-bot

    📣 @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:

    1. Make sure you've read and understood the contributing guidelines.
    2. 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.
    3. 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.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  7. yusufdeveloper2903 commented on Sep 25, 2026

    @yusufdeveloper2903
    Contributor

    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:

    type SetVacationDelegateParams = {
    creator: string;
    vacationerEmail?: string;
    vacationDelegateEmail: string;
    overridePolicyDiffWarning: boolean;
    domainAccountID?: number;
    };

    the NVP type has no date field:

    type BaseVacationDelegate = {
    /** Email of the user that set the vacation delegate */
    creator?: string;
    /** Email of the vacation delegate */
    delegate?: string;
    /** Array of emails for users that the current user is delegating for */
    delegatorFor?: string[];

    and the only place the delegate appears is a section at the bottom of the Status page:

    <View style={[styles.mb2, styles.mt6]}>
    <Text style={[styles.headerText, styles.mh5, styles.mb2]}>{translate('common.vacationDelegate')}</Text>
    {hasActiveDelegations ? (
    <DelegatorList
    delegators={vacationDelegate?.delegatorFor}
    message={translate('statusPage.cannotSetVacationDelegate')}
    />
    ) : (
    <>
    <Text style={[styles.mh5, styles.mb1]}>{translate('statusPage.setVacationDelegate')}</Text>
    <VacationDelegateMenuItem
    vacationDelegate={vacationDelegate}
    errors={getVacationDelegateErrors(vacationDelegate)}
    pendingAction={vacationDelegate?.pendingAction}
    onCloseError={() => clearVacationDelegateError(vacationDelegate?.previousDelegate)}
    onPress={() => Navigation.navigate(ROUTES.SETTINGS_VACATION_DELEGATE)}
    />
    </>
    )}
    </View>

    The Profile row for Status only borrows the delegate's error dot. It never shows the delegate:

    {
    description: translate('statusPage.status'),
    title: emojiCode ? `${emojiCode} ${currentUserPersonalDetails?.status?.text ?? ''}` : '',
    pageRoute: ROUTES.SETTINGS_STATUS,
    brickRoadIndicator: isEmptyObject(getVacationDelegateErrors(vacationDelegate)) ? undefined : CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR,
    testID: 'status-menu-item',
    sentryLabel: CONST.SENTRY_LABEL.SETTINGS_PROFILE.STATUS,
    },

    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:

    useEffect(() => {
    if (!currentUserPersonalDetails.status?.clearAfter) {
    return;
    }
    const currentTime = new Date();
    const clearAfterTime = new Date(currentUserPersonalDetails.status.clearAfter);
    if (Number.isNaN(clearAfterTime.getTime())) {
    return;
    }
    const subMillisecondsTime = clearAfterTime.getTime() - currentTime.getTime();

    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:

    const parameters: SetVacationDelegateParams = {
    creator,
    vacationDelegateEmail: delegate,
    overridePolicyDiffWarning: shouldOverridePolicyDiffWarning,
    };

    App/src/libs/actions/Domain.ts

    Lines 1759 to 1765 in 3549627

    const parameters: SetVacationDelegateParams = {
    creator,
    vacationerEmail: vacationer,
    vacationDelegateEmail: delegate,
    overridePolicyDiffWarning: true,
    domainAccountID,
    };

         vacationDelegateEmail: delegate,
    +    clearAfter,
         overridePolicyDiffWarning: shouldOverridePolicyDiffWarning,

    3. UI, per the mockups.

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

    Demo branch: yusufdeveloper2903/App@3549627...proposal/102202

    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.
  8. Siwanadfour commented on Sep 25, 2026

    @Siwanadfour

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~015f18f27caf2b5875

  9. melvin-bot commented on Sep 25, 2026

    @melvin-bot

    ⚠️ Invalid email. Please make sure to create an Expensify account with this email first here.

  10. 28 remaining items

  11. luacmartins commented on Oct 8, 2026

    @luacmartins
    Contributor

    @ikevin127 PRs are in production

  12. 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
  13. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    @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]

  14. ikevin127 commented on Oct 16, 2026

    @ikevin127
    Contributor

    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

    • 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

    1. Sign in as Account A and go to Account > Profile. Verify a Vacation delegate row shows right after Status, with no value.
    2. 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

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

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

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

    4. 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".
    5. 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.

    6. 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".

    7. 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.
    8. Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".

    Edit and remove

    1. 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.
    2. 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".
    3. 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.
    4. 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".
    5. 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.
    6. 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.
    7. 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.
    8. Open the form and click Remove vacation delegate. Verify the RHP closes and the Profile row shows Vacation delegate with no value.

    No date

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

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

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

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

    2. Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.

    While you're someone else's delegate

    1. 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.
    2. Open settings/profile/vacation-delegate directly. Verify the same message shows.

    Domain admin

    1. 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.
    2. 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.
    3. 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.
    4. Sign in as M and open Account > Profile. Verify the row shows Account B with the same "Until Oct 13, 2026 5:30 PM".
    5. 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)

    1. 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.
    2. 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)

    1. While online, set Account B as your vacation delegate, clearing tomorrow at 5:30 PM. Then go offline.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. Go offline again and open the form. Click Choose delegate. Verify the offline blocking view shows, and its back button returns to the form.
    7. 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.
    8. 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.
    9. 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

    1. 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.
    2. 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.
    3. Go offline again, open the form and click Choose delegate. Verify the offline blocking view shows.
    4. 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.

    Do we agree 👍 or 👎.

  15. ikevin127 commented on Oct 16, 2026

    @ikevin127
    Contributor

    💸 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:

    • 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

    1. Sign in as Account A and go to Account > Profile. Verify a Vacation delegate row shows right after Status, with no value.
    2. 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

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

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

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

    4. 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".
    5. 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.

    6. 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".

    7. 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.
    8. Refresh the page. Verify the Profile row still shows Account B and "Until Oct 13, 2026 5:30 PM".

    Edit and remove

    1. 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.
    2. 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".
    3. 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.
    4. 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".
    5. 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.
    6. 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.
    7. 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.
    8. Open the form and click Remove vacation delegate. Verify the RHP closes and the Profile row shows Vacation delegate with no value.

    No date

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

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

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

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

    2. Remove the delegate and repeat step 22, but click Skip instead of Invite. Verify the same result.

    While you're someone else's delegate

    1. 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.
    2. Open settings/profile/vacation-delegate directly. Verify the same message shows.

    Domain admin

    1. 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.
    2. 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.
    3. 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.
    4. Sign in as M and open Account > Profile. Verify the row shows Account B with the same "Until Oct 13, 2026 5:30 PM".
    5. 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)

    1. 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.
    2. 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)

    1. While online, set Account B as your vacation delegate, clearing tomorrow at 5:30 PM. Then go offline.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. Go offline again and open the form. Click Choose delegate. Verify the offline blocking view shows, and its back button returns to the form.
    7. 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.
    8. 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.
    9. 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

    1. 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.
    2. 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.
    3. Go offline again, open the form and click Choose delegate. Verify the offline blocking view shows.
    4. 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.

    Test coverage: #102472 (comment)

    Additional fixes: ~

    Deploy blockers audited: ~

    Multiple PRs: One docs follow-up PR #104126.

    cc @luacmartins for increase request confirmation 🙌

  16. melvin-bot commented on Oct 22, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionExternalAdded to denote the issue can be worked on by a contributorWeeklyKSv2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions