Skip to content

Split scan - Clear button on Date field does not clear date field on split preview #96658

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.4.41-0
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #96228
Email or phone of affected tester (no customers): sdsjodijnsdojsosjio@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Mac 26.5 / Chrome
App Component: Money Requests

Action Performed:

  1. Go to staging.new.expensify.com
  2. Open 1:1 DM.
  3. Create a split scan expense with invalid receipt.
  4. Click on the split preview.
  5. Click Show more.
  6. Click X button on Date field.

Expected Result:

Date field will be cleared.

Actual Result:

Date field is not cleared. Clear button is not responsive.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7213642_1784648851832.23.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7213642

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jul 21, 2026
  2. applause-bot commented on Jul 21, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/4d6ab0b1ace658ab31cf9dc2d15f83e71e5da4b1f83fc4194bad26c6438efafd

  3. melvin-bot commented on Jul 21, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #96228

  4. melvin-bot commented on Jul 21, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  5. github-actions commented on Jul 21, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  6. MelvinBot commented on Jul 21, 2026

    @MelvinBot
    Contributor

    Issue Analysis

    Root Cause: This is a regression from #96228 (removed the isNewManualExpenseFlowEnabled beta). That PR changed DateField.tsx:107 so the inline DatePicker — which brings its own clear "X" — now renders for any editable confirmation field, including the split-preview edit flow that previously used a navigate-only menu item with no clear button. The clear button does nothing here because of a write/read asymmetry that only exists in the split path.

    The clear button appears even for an invalid-receipt scan because buildOptimisticTransaction seeds created to today's date (TransactionUtils/index.ts:451: created || DateUtils.getDBTime()), so the field is non-empty and the X renders. When you click X, the split branch of handleDateChange calls setDraftSplitTransaction(..., {created: ''}) → getUpdatedTransaction writes the empty value to modifiedCreated (index.ts:634-635). But getCreated uses a truthy fallback (index.ts:1371): modifiedCreated ? modifiedCreated : created || ''. An empty modifiedCreated is falsy, so it falls back to the original non-empty created, the field re-renders unchanged, and the clear looks unresponsive.

    The regular (non-split) flow is unaffected because it clears via setMoneyRequestCreated, which writes created directly (MoneyRequest.ts:854-856), so an empty date sticks there.

    Recommended Fix (surgical, deploy-blocker safe): Hide the clear button in the split-editing path — pass shouldHideClearButton={isEditingSplitBill} to the DatePicker in DateField.tsx:110. This restores parity with the pre-#96228 behavior (the split flow never offered an inline clear) without reverting the whole beta removal. The prop already exists (DatePicker/index.tsx:46).

    Investigation details, alternatives, and evidence chain

    Repro chain (invalid-receipt split scan):

    1. Split scan → startSplitBill → buildOptimisticTransaction with no created → created seeded to DateUtils.getDBTime() (today). SmartScan would normally overwrite it, but an invalid receipt makes SmartScan fail, so it stays at today.
    2. Amount stays 0 / required fields empty → isEditingSplitBill === true → fields are editable (DynamicSplitBillDetailsPage.tsx:90-91,168). The confirmation list reads/writes the SPLIT_TRANSACTION_DRAFT.
    3. getCreated returns the non-empty created (today) → DatePicker value non-empty → clear X renders.
    4. Click X → onInputChange('') → handleDateChange(''). '' !== iouCreated so the newDate === iouCreated early-return does not fire; it proceeds to the split branch.
    5. setDraftSplitTransaction(..., {created: ''}) → getUpdatedTransaction sets modifiedCreated = ''.
    6. getCreated = modifiedCreated ? modifiedCreated : created || '' → empty modifiedCreated is falsy → returns the original created (today). Field reverts → "clear does nothing."

    Note isCreatedMissing also stays false (created is non-empty), so no required-field error appears either — the field just silently snaps back.

    Alternative fixes considered:

    • Make clearing actually work in the split path (deeper fix): the real asymmetry is that getCreated's truthy fallback can't represent an explicitly-cleared modifiedCreated. Fixing that touches shared getCreated/getUpdatedTransaction and is broad/risky for a deploy blocker — better as a follow-up if product wants the split date to be clearable (matching the new manual-expense flow, where clearing sets an empty date + required error).
    • Revert feat: remove isNewManualExpenseFlowEnabled beta flag #96228: reliably fixes it but reverts an entire intended beta removal across the whole manual-expense flow — heavy-handed for this edge case.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/96658) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting this proposal.

  7. trjExpensify commented on Jul 21, 2026

    @trjExpensify
    Contributor

    Spilt bill P2P flow, lower priority. We should fix it though, it should be able to be cleared and should not show a timestamp just the date. CC: @thelullabyy

  8. yuwenmemon commented on Jul 21, 2026

    @yuwenmemon
    Contributor

    The offending PR that caused this deploy blocker was reverted with #96661.

    @Expensify/applause please retest this issue on the latest staging build now that the PR has been reverted, and confirm whether it still reproduces.

  9. puneetlath commented on Jul 21, 2026

    @puneetlath
    Contributor

    PR reverted.

  10. thelullabyy commented on Jul 29, 2026

    @thelullabyy
    Contributor

    @MelvinBot Please investigate and give me patch file to test your fix solution

  11. MelvinBot commented on Jul 29, 2026

    @MelvinBot
    Contributor

    Patch to test — Split Date field: clearable + date-only

    Here's a patch implementing the behavior trjExpensify asked for (clearable, and show just the date, not a timestamp) — not the earlier "hide the clear button" proposal.

    ⚠️ Note the original deploy-blocker PR (Expensify/App#96228) was already reverted by Expensify/App#96661, so on main this split-preview path only renders the inline DatePicker (with the clear "X") when the NEW_MANUAL_EXPENSE_FLOW beta is enabled — which is why it reproduced on staging. The patch fixes the underlying data asymmetry so it's correct whenever that path renders.

    Root cause (two independent problems):

    1. Shows a timestamp — the split-bill draft seeds created with a full DB timestamp (DateUtils.getDBTime()) at TransactionUtils/index.ts:451, and the confirmation Date field renders that raw value (selectors.ts:33 → raw getCreated). The manual flow doesn't hit this because it seeds a date-only string.
    2. Clear does nothing — clearing writes modifiedCreated = '' (via setDraftSplitTransaction → getUpdatedTransaction), but getCreated used a truthy check (modifiedCreated ? … : created), so the empty string is ignored and it falls back to the seeded created. The DatePicker then re-syncs its input back to that value, so the clear looks unresponsive.

    The fix (2 files):

    • getCreated treats an explicitly-set-empty modifiedCreated as an intentional clear (fixes Fix spaces #2). This only changes the split path — the manual flow clears via setMoneyRequestCreated, which writes created directly and is unaffected.
    • The confirmation Date selector uses getFormattedCreated (date-only) instead of raw getCreated (fixes Some initial fixes and code style updates #1). No effect on the manual flow, which is already date-only.
    Patch — save as 96658-clear-date-split.patch and git apply
    diff --git a/src/components/MoneyRequestConfirmationList/sections/selectors.ts b/src/components/MoneyRequestConfirmationList/sections/selectors.ts
    index 8a58713d88..9301c2c038 100644
    --- a/src/components/MoneyRequestConfirmationList/sections/selectors.ts
    +++ b/src/components/MoneyRequestConfirmationList/sections/selectors.ts
    @@ -1,8 +1,8 @@
     import {canSendInvoice} from '@libs/PolicyUtils';
     import {
         getCategory,
    -    getCreated,
         getCurrency,
    +    getFormattedCreated,
         getMerchant,
         getTagForDisplay,
         hasReceipt,
    @@ -30,7 +30,9 @@ const dateStateSelector = (t: OnyxEntry<Transaction>): DateState | undefined =>
             return undefined;
         }
         return {
    -        iouCreated: getCreated(t),
    +        // Use the formatted (date-only) value so a draft seeded with a full DB timestamp
    +        // (e.g. the split-bill draft) renders just the date, never a timestamp.
    +        iouCreated: getFormattedCreated(t),
             isMissing: isCreatedMissing(t),
             hasReceipt: hasReceipt(t),
         };
    diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
    index 230dfd06a5..5d7b70a644 100644
    --- a/src/libs/TransactionUtils/index.ts
    +++ b/src/libs/TransactionUtils/index.ts
    @@ -1388,8 +1388,15 @@ function getTagForDisplay(transaction: OnyxEntry<Pick<Transaction, 'tag'>>, tagI
     }
     
     function getCreated(transaction: OnyxInputOrEntry<Transaction>): string {
    +    const modifiedCreated = transaction?.modifiedCreated;
    +    // A date that was explicitly cleared is stored as an empty `modifiedCreated`, so it must take
    +    // precedence over the original `created`. Only fall back to `created` when `modifiedCreated` was
    +    // never set (e.g. a freshly seeded draft), otherwise clearing the date can never stick.
    +    if (modifiedCreated !== undefined) {
    +        return modifiedCreated;
    +    }
         // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
    -    return transaction?.modifiedCreated ? transaction.modifiedCreated : transaction?.created || '';
    +    return transaction?.created || '';
     }
     
     /**

    Apply with:

    git apply 96658-clear-date-split.patch

    How to test (with the NEW_MANUAL_EXPENSE_FLOW beta enabled):

    1. Open a 1:1 DM → create a split scan expense with an invalid receipt.
    2. Open the split preview → Show more.
    3. The Date field should now show just the date (no HH:mm:ss).
    4. Click the X on the Date field → the field should now clear and stay empty.

    I verified this by tracing the read/write paths and confirming the patch applies cleanly and is type-safe; I did not run a live device test, so please confirm the UI behavior on your end. Note I couldn't run npm run fmt/lint/typecheck in this environment — please run those before opening a PR.

    Why not just hide the clear button, and a note on required-field validation

    Hiding the button (the earlier proposal) would have removed the affected surface but wouldn't give the split flow parity with the new manual-expense flow, which is what trjExpensify asked for. This patch instead fixes the actual asymmetry (getCreated couldn't represent an explicitly-cleared date).

    After clearing, getCreated returns '' and the field renders empty. isCreatedMissing still returns false here because the draft's base created (the seed) is non-empty — so no required-field error is raised on the cleared split date. trjExpensify's two stated requirements (clearable + date-only) are met; if product also wants a cleared split date to block submission with a required-field error, that's a small follow-up in isCreatedMissing.

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

Metadata

Metadata

Assignees

Labels

BugSomething is broken. Auto assigns a BugZero manager.EngineeringHourlyKSv2

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions