Repository navigation
Split scan - Clear button on Date field does not clear date field on split preview #96658
Description
Activity
- addedDeployBlockerCashThis issue or pull request should block deploymentThis issue or pull request should block deploymentBugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Jul 21, 2026 While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/4d6ab0b1ace658ab31cf9dc2d15f83e71e5da4b1f83fc4194bad26c6438efafdYou have been assigned to this deploy blocker because you recently merged this PR: #96228
💬 A slack conversation has been started in #expensify-open-source
github-actions commented
on Jul 21, 2026 on Jul 21, 2026 – with GitHub ActionsContributorMore actions👋 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:
- Identify the pull request that introduced this issue and revert it.
- Find someone who can quickly fix the issue.
- Fix the issue yourself.
Issue Analysis
Root Cause: This is a regression from #96228 (removed the
isNewManualExpenseFlowEnabledbeta). That PR changedDateField.tsx:107so the inlineDatePicker— 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
buildOptimisticTransactionseedscreatedto 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 ofhandleDateChangecallssetDraftSplitTransaction(..., {created: ''})→getUpdatedTransactionwrites the empty value tomodifiedCreated(index.ts:634-635). ButgetCreateduses a truthy fallback (index.ts:1371):modifiedCreated ? modifiedCreated : created || ''. An emptymodifiedCreatedis falsy, so it falls back to the original non-emptycreated, the field re-renders unchanged, and the clear looks unresponsive.The regular (non-split) flow is unaffected because it clears via
setMoneyRequestCreated, which writescreateddirectly (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 theDatePickerinDateField.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):
- Split scan →
startSplitBill→buildOptimisticTransactionwith nocreated→createdseeded toDateUtils.getDBTime()(today). SmartScan would normally overwrite it, but an invalid receipt makes SmartScan fail, so it stays at today. - Amount stays
0/ required fields empty →isEditingSplitBill === true→ fields are editable (DynamicSplitBillDetailsPage.tsx:90-91,168). The confirmation list reads/writes theSPLIT_TRANSACTION_DRAFT. getCreatedreturns the non-emptycreated(today) →DatePickervalue non-empty → clear X renders.- Click X →
onInputChange('')→handleDateChange('').'' !== iouCreatedso thenewDate === iouCreatedearly-return does not fire; it proceeds to the split branch. setDraftSplitTransaction(..., {created: ''})→getUpdatedTransactionsetsmodifiedCreated = ''.getCreated = modifiedCreated ? modifiedCreated : created || ''→ emptymodifiedCreatedis falsy → returns the originalcreated(today). Field reverts → "clear does nothing."
Note
isCreatedMissingalso staysfalse(createdis 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-clearedmodifiedCreated. Fixing that touches sharedgetCreated/getUpdatedTransactionand 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.- Split scan →
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
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.
PR reverted.
- removedDeployBlockerCashThis issue or pull request should block deploymentThis issue or pull request should block deployment
on Jul 22, 2026 @MelvinBot Please investigate and give me patch file to test your fix solution
Reacted by melvin-botPatch to test — Split Date field: clearable + date-only
Here's a patch implementing the behavior
trjExpensifyasked 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 byExpensify/App#96661, so onmainthis split-preview path only renders the inlineDatePicker(with the clear "X") when theNEW_MANUAL_EXPENSE_FLOWbeta 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):
- Shows a timestamp — the split-bill draft seeds
createdwith a full DB timestamp (DateUtils.getDBTime()) atTransactionUtils/index.ts:451, and the confirmation Date field renders that raw value (selectors.ts:33→ rawgetCreated). The manual flow doesn't hit this because it seeds a date-only string. - Clear does nothing — clearing writes
modifiedCreated = ''(viasetDraftSplitTransaction→getUpdatedTransaction), butgetCreatedused a truthy check (modifiedCreated ? … : created), so the empty string is ignored and it falls back to the seededcreated. TheDatePickerthen re-syncs its input back to that value, so the clear looks unresponsive.
The fix (2 files):
getCreatedtreats an explicitly-set-emptymodifiedCreatedas an intentional clear (fixes Fix spaces #2). This only changes the split path — the manual flow clears viasetMoneyRequestCreated, which writescreateddirectly and is unaffected.- The confirmation Date selector uses
getFormattedCreated(date-only) instead of rawgetCreated(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.patchandgit applydiff --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_FLOWbeta enabled):- Open a 1:1 DM → create a split scan expense with an invalid receipt.
- Open the split preview → Show more.
- The Date field should now show just the date (no
HH:mm:ss). - 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/typecheckin 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
trjExpensifyasked for. This patch instead fixes the actual asymmetry (getCreatedcouldn't represent an explicitly-cleared date).After clearing,
getCreatedreturns''and the field renders empty.isCreatedMissingstill returnsfalsehere because the draft's basecreated(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 inisCreatedMissing.- Shows a timestamp — the split-bill draft seeds
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
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:
Expected Result:
Date field will be cleared.
Actual Result:
Date field is not cleared. Clear button is not responsive.
Workaround:
Unknown
Platforms:
Screenshots/Videos
Bug7213642_1784648851832.23.mp4
View all open jobs on GitHub