Skip to content

Split the confirmation footer into a dispatcher and per-type variants pt1 - #99578

Merged
mountiny merged 16 commits into
Expensify:mainfrom
callstack-internal:VickyStash/refactor/99449-confirmation-footers-pt1
Sep 1, 2026
Merged

Split the confirmation footer into a dispatcher and per-type variants pt1#99578
mountiny merged 16 commits into
Expensify:mainfrom
callstack-internal:VickyStash/refactor/99449-confirmation-footers-pt1

Conversation

@VickyStash

@VickyStash VickyStash commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Refactor with no behavior change.

MoneyRequestConfirmationListFooter was one component serving every expense type, selecting behavior at runtime through boolean flags passed down from the list. This PR turns it into a folder with a dispatcher and per-type variants, and migrates the first two types (PerDiem and Time).

Updates:

  1. The footer becomes a folder. MoneyRequestConfirmationListFooter.tsx splits into index.tsx for dispatch, variants/DefaultFooter.tsx for the previous body, and types.ts for the shared props type.

  2. PerDiemFooter is the first variant. It renders the per-diem subrate fields and the shared field list and nothing else, because per-diem shows no receipt on the confirmation page, is never a distance or scan request, and never enters compact mode.

  3. TimeFooter is the second variant. TimeDetailsFields renders Amount, Description and the hours/rate fields with no conditionals.

  4. ConfirmationFieldList stops rendering TransactionDetailsFields. It takes a renderTransactionDetailsFields callback and invokes it in the same position, so render order does not change, and each variant supplies the details component its own type needs.

  5. ConfirmationFieldsProvider moves up to MoneyRequestConfirmationList. MoneyRequestConfirmationListFooterProps drops from 28 props to 13.

  6. The confirmation mount span gains a request-type attribute.

High level of the new structure that is going to be: comment

Fixed Issues

$ #99449
PROPOSAL: N/A

Tests

  • Verify that no errors appear in the JS console

This is a refactor with no intended behavior change, so testing is regression-focused: every expense type must render and submit exactly as before.

  1. Create a per-diem expense (workspace with per-diem enabled). Verify the confirmation screen looks as before. Submit and verify the expense is created.
  2. Create a time expense (workspace with time tracking enabled). Verify the confirmation screen looks as before. Submit and verify the expense is created.
  3. Create a manual expense. Verify the confirmation screen looks as before. Submit and verify the expense is created.
  4. Create a scan expense. Verify the confirmation screen looks as before. Submit and verify the expense is created.
  5. Create a distance expense (map). Verify the confirmation screen looks as before. Submit and verify the expense is created.
  6. Create a distance expense via odometer.Verify the confirmation screen looks as before. Submit and verify the expense is created.
  7. Create a split expense with two participants. Verify the confirmation screen looks as before. Submit and verify the expense is created.
  8. Create an invoice. Verify the confirmation screen looks as before, and the invoice is submitted successfully.
  9. From a tracked expense, use Submit to move it to a workspace. Verify the confirmation screen renders and submits as it did before.

Offline tests

Same, as in Tests section

QA Steps

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

  • Verify that no errors appear in the JS console

Same, as in Tests section

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Per-diem
per-diem.mp4
Time
time.mp4
Manual
manual.mp4
Scan
scan.mp4
Distance
distance.mp4
Split
split.pay.mp4
Invoice
invoice.mp4
Submit
submit.mp4

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/components/MoneyRequestConfirmationList.tsx 94.20% <100.00%> (-0.05%) ⬇️
...eyRequestConfirmationList/sections/AmountField.tsx 44.44% <100.00%> (+0.47%) ⬆️
...uestConfirmationList/sections/DescriptionField.tsx 77.41% <100.00%> (ø)
...neyRequestConfirmationList/sections/TimeFields.tsx 85.71% <100.00%> (ø)
...ups/TransactionDetailsFields/TimeDetailsFields.tsx 100.00% <100.00%> (ø)
...ter/fieldGroups/TransactionDetailsFields/index.tsx 100.00% <100.00%> (ø)
...ConfirmationListFooter/sections/PerDiemSection.tsx 0.00% <ø> (-33.34%) ⬇️
...ConfirmationListFooter/sections/ReceiptSection.tsx 93.54% <100.00%> (+0.69%) ⬆️
...tConfirmationListFooter/variants/DefaultFooter.tsx 100.00% <100.00%> (ø)
...uestConfirmationListFooter/variants/TimeFooter.tsx 100.00% <100.00%> (ø)
... and 5 more
... and 1337 files with indirect coverage changes

@VickyStash VickyStash changed the title [WIP] Split the confirmation footer into a dispatcher and per-type variants [WIP] Split the confirmation footer into a dispatcher and per-type variants pt1 Aug 27, 2026
@VickyStash

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 7ebcfba508

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@VickyStash

Copy link
Copy Markdown
Contributor Author

@claude review

@VickyStash

Copy link
Copy Markdown
Contributor Author

@MelvinBot what do you think about the changes?

@MelvinBot

Copy link
Copy Markdown
Contributor

Overall this is a clean, well-scoped execution of step 1 of the plan — the dispatcher + per-type variant split reads exactly as described in #99449, the JSDoc on the new modules is genuinely helpful, and the render-prop seam (renderTransactionDetailsFields) is a nice way to let ConfirmationFieldList stay type-agnostic. Direction looks right. A few things worth confirming before this comes out of WIP:

Routing correctly mirrors the old runtime guards ✅ — the dispatcher conditions are the same predicates the sections used to self-check, so the behavior should be preserved:

  • PerDiem: isPerDiem && action !== SUBMIT matches the guard removed from PerDiemSection. PerDiem+SUBMIT falls through to DefaultFooter, which no longer renders PerDiemSection at all — net effect is the same as the old internal return null.
  • Time: isTime && action === CREATE; any other time action falls back to DefaultFooter, which still handles time via TransactionDetailsFields. Safe.

Dropping InvoiceSenderSection / DistanceMapSection from the variants is safe — both self-guard (return null unless invoice/distance respectively), and per-diem/time are neither, so omitting them changes nothing. Worth a sanity check that time/per-diem can never be an invoice or distance type, since that's the assumption the fork now bakes in structurally rather than at runtime.

Watch items:

  1. Coverage regression on the per-diem path. Codecov shows PerDiemSection.tsx dropping to 0% (−33%) — the tests that exercised the removed guard branch no longer reach it. Adding a test that renders through PerDiemFooter would restore it and lock in the routing.

  2. TimeDetailsFields structurally omits Merchant (comment here) rather than honoring fieldVisibility.merchant. It's well-justified for time+CREATE, but it's a latent divergence from DefaultFooter if the Time routing ever broadens — fine as-is, just flagging the coupling.

  3. Minor — explicitness in TimeFooter. It spreads <ReceiptSection {...receiptOptions} /> while DefaultFooter passes each field with explicit ?? false defaults. It now type-checks because you made those ReceiptSection props optional, but the two footers wiring the same section differently is easy to drift. Similarly distanceRateCurrency = CONST.CURRENCY.USD in AmountField is a reasonable fallback (only read when isDistanceRequest, which is false for time), but a hardcoded currency default can quietly mask a genuinely-missing prop elsewhere — worth a second look.

  4. Duplication is expected here — the ConfirmationFieldsProvider + ConfirmationFieldList scaffold is repeated across all three variants. That's the intended tradeoff of the fork strategy (isolation over DRY), so no objection, just noting it's the thing to keep an eye on as more variants land.

TelemetrySpanManager change is tidy and the "tag on end, not start" comment explains the why well.


view run

@VickyStash VickyStash changed the title [WIP] Split the confirmation footer into a dispatcher and per-type variants pt1 Split the confirmation footer into a dispatcher and per-type variants pt1 Aug 28, 2026
@VickyStash
VickyStash marked this pull request as ready for review August 28, 2026 10:26
@VickyStash
VickyStash requested review from a team as code owners August 28, 2026 10:26
@melvin-bot
melvin-bot Bot requested review from situchan and removed request for a team August 28, 2026 10:26
@melvin-bot

melvin-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

@situchan Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from joekaufmanexpensify and removed request for a team August 28, 2026 10:26
Comment thread src/components/MoneyRequestConfirmationListFooter/ConfirmationFieldList.tsx Outdated
Comment thread src/components/MoneyRequestConfirmationListFooter/types.ts Outdated
@VickyStash

Copy link
Copy Markdown
Contributor Author

@claude review

@situchan

Copy link
Copy Markdown
Contributor

@codex review

@situchan

Copy link
Copy Markdown
Contributor

@MelvinBot review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: e885049106

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@MelvinBot

Copy link
Copy Markdown
Contributor

Re-reviewed at e8850491. The big change since my last pass is swapping the renderTransactionDetailsFields render-prop for a children + DetailsFieldsContext seam. That reads cleaner than the render prop — ConfirmationFieldList publishes the derived flags once (fieldVisibility, isCompactMode, iouCurrencyCode, shouldNavigateToUpgradePath, shouldSelectPolicy) and each variant's details component pulls only what it needs. The useDetailsFields() throw-if-null guard is a nice touch. Direction and execution both look right — behavior is preserved. I traced the two migrated variants against the old runtime guards and smoke-tested the fallback path on web (below).

The two structural assumptions the variants now bake in are correct ✅

Both TimeDetailsFields and PerDiemDetailsFields drop fields that TransactionDetailsFields used to gate at runtime, so the fork is only safe if those gates were already guaranteed for the routed type:

  • Time (TimeDetailsFields) hardcodes shouldShowTimeRequestFields and omits Merchant/Distance/Rate. Merchant/Distance/Rate are gated by fieldVisibility.{merchant,distance,rate} which are false for a time request, so omitting them matches. It also drops the !isCompactMode guard and isParticipantPickerVisible — both no-ops here, since time never enters compact mode and isAmountFieldDisabled is always true for time (AmountField.tsx:102), which short-circuits the autofocus effect and the editable input that isParticipantPickerVisible feeds.
  • PerDiem (PerDiemDetailsFields) renders only Description. For per-diem, amount is false (shouldShowAmountField = !isPerDiemRequest), and merchant/distance/rate/time are all false too — so the old path also rendered only Description. Dropping ReceiptSection/InvoiceSenderSection/DistanceMapSection is safe since all three self-guard to null for per-diem.

Smoke test (web) — DefaultFooter path

The dispatcher routes manual / scan / distance / split / invoice to DefaultFooter, which is byte-for-byte the old footer body. Verified on dev NewDot web:

Step Status Observation
Manual expense confirmation renders (Amount, Merchant, Description, Category, Date, footer) ✅ pass All fields present; footer button Create …expense correct; no blank sections/crash
Manual expense submits ✅ pass Landed in report; POST /api/TrackExpense returned 200
Distance confirmation renders Distance + Rate ✅ pass Distance 12.00 km, Rate €0.3 / km, auto-computed amount all render
No footer-related errors ✅ pass No error banners / blank sections; no 4xx/5xx in the network dump (console capture isn't available for web agent-device sessions)

Per-diem and time weren't exercised — the test account had no per-diem/time-enabled workspace — so those two variants rest on the code-trace above plus your recorded screenshots.

Non-blocking watch items (unchanged from last pass)
  1. Per-diem coverage regression. Codecov still shows PerDiemSection.tsx at 0% (−33%) — the removed two-level guard branch is no longer reached. A test rendering through PerDiemFooter would restore it and lock the routing in.
  2. {...receiptOptions} spread drift. DefaultFooter and TimeFooter both spread <ReceiptSection {...receiptOptions} /> and now rely on the new optional-prop defaults on ReceiptSection. It type-checks and is behavior-equivalent, but the two footers wiring the same section by spread is easy to drift — worth a mental note as more variants land.
  3. distanceRateCurrency = CONST.CURRENCY.USD default in AmountField. Fine here (only read when isDistanceRequest, false for time), but a hardcoded currency fallback can quietly mask a genuinely-missing prop in a future caller.

Nothing blocking from me — the refactor is sound and the fallback path is verified. LGTM once the per-diem coverage note is addressed (or consciously deferred).


view run

@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

No product review needed.

@joekaufmanexpensify
joekaufmanexpensify removed their request for review August 28, 2026 14:35
@mountiny

Copy link
Copy Markdown
Contributor

@situchan lets get this one reviewed 🙌 thank you!

@situchan

situchan commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari
test1.mov
test2.mov

@situchan situchan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NAB comments:

Comment thread src/components/MoneyRequestConfirmationListFooter/index.tsx Outdated
Comment thread src/components/MoneyRequestConfirmationListFooter/variants/DefaultFooter.tsx Outdated
Comment thread src/components/MoneyRequestConfirmationListFooter/variants/DefaultFooter.tsx Outdated
@melvin-bot
melvin-bot Bot requested a review from mountiny September 1, 2026 04:36
@MelvinBot

Copy link
Copy Markdown
Contributor

No functional regressions found at e8850491. Two independent old-vs-new equivalence audits, a repo-wide npm run typecheck, the footer unit tests, and a web smoke test all agree the render output is unchanged for every expense type. What I did find is a coverage regression that leaves the riskiest part of this refactor unguarded — worth fixing before merge.

The one thing I'd fix

The props→context wiring is now untested, and it's the easiest thing to get silently wrong.

Before this PR the footer owned ConfirmationFieldsProvider, so tests/ui/MoneyRequestReportFooter.tsx exercised the real mapping from props to isPerDiemRequest / isTimeRequest / fieldVisibility. The provider moved to MoneyRequestConfirmationList.tsx:552, and the test now hand-builds that context (tests/ui/MoneyRequestReportFooter.tsx:83-93).

Net effect: swapping isPerDiemRequest and isTimeRequest at the new provider call site would compile, ship, route every per-diem expense to the wrong footer, and CI would stay green. That's exactly the failure mode a dispatcher refactor introduces, and nothing tests it now.

The whole per-diem branch is also 0% covered. Measured with npm test -- tests/ui/MoneyRequestReportFooter.tsx tests/ui/TimeExpenseConfirmationTest.tsx --coverage:

Module Stmts Uncovered
MoneyRequestConfirmationListFooter/index.tsx 77.8% L21 — the PerDiemFooter return
variants/PerDiemFooter.tsx 0% L9-27
sections/PerDiemSection.tsx 0% L25-32
PerDiemDetailsFields.tsx 0% L18-22
variants/TimeFooter.tsx / TimeDetailsFields.tsx 100%

Time is fully covered because tests/ui/TimeExpenseConfirmationTest.tsx exists. There is no per-diem equivalent anywhere in tests/. One per-diem sibling to that file would cover index.tsx:21, all three per-diem modules, and restore the provider wiring coverage in a single test. The Codecov −33% on PerDiemSection is separate and benign — that third was only the removed guard's early return, which now lives in the dispatcher and is still covered.

Regression audit — clean, with the reasoning

Every combination routes identically (verified two ways)
  • DefaultFooter is a faithful copy of the old body. The only element it drops is PerDiemSection, and the dispatcher predicate isPerDiemRequest && action !== SUBMIT is the exact complement of the guard removed from PerDiemSection — so it returned null in 100% of the cases that now reach DefaultFooter. ReceiptOptions has no showMoreFields/policy key, so {...receiptOptions} can't clobber the explicit props, and the new = false default params fire on undefined identically to the old ?? false.
  • ConfirmationFieldList children land in the same slot. {children} sits exactly where the inline <TransactionDetailsFields> did — after the compact banner, before ClassificationFields. All five values moved to DetailsFieldsContext are byte-identical to the props they replaced.
  • All 15 dropped props map 1:1 through ConfirmationFieldsProvider. isNewManualExpenseFlowEnabled was the only value the old footer computed locally, and the caller's expression at MoneyRequestConfirmationList.tsx:227 is the identical unchanged isBetaEnabled call.
  • TimeFooter's dropped Merchant is provably dead: shouldShowMerchant includes (!isTimeRequest || action !== CREATE), which is exactly the TimeFooter gate negated. Same for per-diem's dropped Amount (shouldShowAmountField: !isPerDiemRequest).
  • compactState omission is inertisCompactMode requires isScanRequest, and iouRequestType is a single scalar, so SCAN/TIME/PER_DIEM are mutually exclusive.
  • InvoiceSenderSection / DistanceMapSection omissions are inert — invoice renders no expense-type tab bar and hard-forces MANUAL, so invoice + per-diem|time is unreachable.
  • Only one remaining PerDiemSection importer exists repo-wide (variants/PerDiemFooter.tsx), reachable only via the guarded dispatcher branch. No unguarded caller.
  • endSpanWithAttributes is setAttributes + the same endSpan — one .end(), and endSpan deletes the map entry so a second call no-ops. Purely additive; nothing was previously stamped that could be dropped.

npm run typecheck passes on all four tsconfigs. npm test -- tests/ui/MoneyRequestReportFooter.tsx passes 5/5.

Web smoke test — pass

DefaultFooter path verified on dev NewDot web. Result: pass, 3 skipped for account/platform limits, not app defects.

Step Status Observation
Manual confirmation renders all fields, correct submit label ✅ pass In order: Amount €250.00, Merchant, Description, Category, Date; button Create €250.00 expense. No blank or duplicated section
Manual expense submits ✅ pass Chat shows tracked €250.00 for Test manual expense; report card renders correctly
Distance renders map + Distance and Rate ✅ pass Mapbox route with markers; Distance 0.74 kilometers, Rate €0.3/kilometer, Amount €0.22 — computed correctly
Split with two participants ✅ pass Paid by + Participants sections, then Amount/Merchant/Description/Date, Split €100.00 button. Submitted: owes €50.00
No JS errors / failed API calls ✅ pass Network dump across the session: all 200s, no 4xx/5xx, no error banners
Scan confirmation ⏭️ skipped agent-device web has no file-upload capability; can't reach the Scan confirmation screen
Per-diem confirmation ⏭️ skipped Test account has no workspaces, so no per-diem-enabled workspace
Time confirmation ⏭️ skipped Same — no workspace on the account

Per-diem and time therefore rest on the code trace above plus your recorded videos — which is the second reason the per-diem unit test matters.

Evidence (4)

Manual expense confirmation screen

Manual expense confirmation screen

Distance expense confirmation with map route, Distance and Rate fields

Distance expense confirmation with map route, Distance and Rate fields

Split expense confirmation with Paid by / Participants sections

Split expense confirmation with Paid by / Participants sections

Split expense submitted successfully

Split expense submitted successfully

Two non-blocking notes

  1. PerDiemFooter drops ReceiptSection entirely, but the thumbnail branch at ReceiptSection.tsx:92 is not gated on isPerDiemRequest — only the empty state below it is. I traced this and it's unreachable today: it needs transaction.receipt.source + .filename (IOURequestStepConfirmation.tsx:283-284), and per-diem drafts carry no receipt, while isLoadingReceipt is odometer-only. Not a bug — just noting that PerDiemFooterProps Omits receiptOptions, so if per-diem ever gains a receipt, TypeScript won't flag it and the thumbnail silently disappears.

  2. TelemetrySpanManager is a real behavior change in a PR described as "refactor with no behavior change" — it now emits iou_type / iou_request_type span attributes. It's correct and harmless (the span is started with no attributes at both start sites, and TelemetrySpanManager renders after the isLoadingTransaction early return so requestType is hydrated). Just flagging that it's unrelated to the footer split, in case it should ride in its own PR.

Nothing blocking. The refactor is sound — I'd just add the per-diem test before merge, since it's the one path with neither automated nor manual verification in this run.


view run

@VickyStash

Copy link
Copy Markdown
Contributor Author

Applied NABs!
@mountiny all yours!

@mountiny mountiny left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cool, thanks for starting on this

@mountiny
mountiny merged commit 1126a28 into Expensify:main Sep 1, 2026
34 of 35 checks passed
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

OSBotify commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

OSBotify commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.66-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🤖 No help site changes are required for this PR, so I did not create a docs PR.

This is an internal component refactor. It changes how the confirmation footer code is organized, not what anyone sees or does in the app.

Why I concluded no docs update is needed

What the diff actually changes. All 19 files are under src/components/, src/pages/iou/, and tests/. The change splits MoneyRequestConfirmationListFooter.tsx into a dispatcher plus per-type variants (DefaultFooter, PerDiemFooter, TimeFooter), moves a context provider up a level, and drops the footer's props from 28 to 13.

No user-facing copy changed. The diff touches no src/languages/*.ts file. The only translate() lines in the diff are the same two calls (iou.automaticallyEnterExpenseDetails and common.showMore) re-indented as code moved between files — the strings themselves are byte-identical. No new labels, buttons, field names, or screens were added, so there is nothing new to name or document.

No behavior changed. The PR is explicitly scoped as a refactor with no intended behavior change, and the structure preserves that: ConfirmationFieldList now takes a renderTransactionDetailsFields callback and invokes it in the same position, so render order on the confirmation screen is unchanged. The test plan is regression-focused across all eight expense types (per diem, time, manual, scan, distance, odometer, split, invoice, submit-from-tracked), with videos attached for each.

The articles that cover these flows stay accurate. The help site documents per diem and time expenses from the user's point of view — for example docs/articles/new-expensify/reports-and-expenses/Create-an-Expense.md and docs/articles/new-expensify/workspaces/Enable-Time-Tracking-and-Set-a-Default-Hourly-Rate.md. Those articles describe the steps a member takes and the fields they fill in. Since the fields, their order, and their labels are all unchanged, every statement in them remains correct.

Per docs/HELP_AUTHORING_GUIDELINES.md, help articles describe user-facing behavior — they do not document internal component structure. A refactor with no visible change has nothing to write about.

One thing to flag for your own review, not for docs: because this refactor is only pt1, PerDiemFooter and TimeFooter deliberately hardcode assumptions that the old shared component evaluated at runtime — per diem shows no receipt, is never a distance or scan request, and never enters compact mode. Those assumptions are correct today. If a future PR makes per diem or time expenses support receipts or compact mode, the variant will silently skip that rendering rather than fail loudly, and that would be a real user-visible change worth documenting. Worth a comment in the variant files if pt2 doesn't already cover it.

@VickyStash — you asked me to close with a note pointing you at the linked help site PR. Since no docs changes were needed, there is no docs PR to link or mark Ready for review, so I've left that line off rather than point you at something that doesn't exist. If you disagree and want an article updated anyway, tell me which one and what it should say, and I'll open the draft PR with the HelpDot label and assign it to you.


view run · view recording

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants