Concierge AI Feature Promo Modal v2#94012
Draft
gijoe0295 wants to merge 7 commits into
Draft
Conversation
…ature-promo-modal" This reverts commit 9586f96.
`openSearchRouter` was pushing the `{isSearchModalOpen: true}` history entry
synchronously, before `close()` finished dismissing any pre-existing modals.
When the AI promo modal closed it ran `Navigation.goBack()` (default
behavior of `FeatureTrainingModal` via `shouldGoBack=true`), which popped
the just-pushed history entry. The resulting `popstate` event fired before
the close callback finished, immediately marking the router as closed.
Move the `pushState` inside the `close()` callback so the history entry is
added only after pre-existing modals' close paths (including any history-
mutating `goBack`) have settled.
Fixes Expensify#93964
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The carousel path bypassed the ScrollView Wrapper that the single-page path of FeatureTrainingModal uses, so in landscape (where the modal is hard-capped at 75% of window height via MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE) there was no scroller to make the buttons reachable when the illustration consumed most of the cap. Lift the Wrapper (ScrollView when shouldUseScrollView, else View) out so both branches share it, and remove the now-redundant landscape maxHeight and wrapperStyles from FeatureTrainingModalCarouselBody. Also drop `isCarousel` from the keyboard scroll-to-end early-return so the carousel benefits from the same keyboard behavior. Fixes Expensify#93968 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On narrow viewports (mWeb BOTTOM_DOCKED) the carousel's outer View had no intrinsic content (everything inside is gated on `carouselViewportWidth > 0`) and no width style, so inside the modal sheet's `fit-content` container it collapsed to 0×0. `onLayout` never fired with a positive width, the carousel never rendered, and the user saw the modal backdrop only. Add `styles.w100` to the outer View on narrow screens so it stretches to the sheet's full width and `onLayout` resolves immediately. The explicit `getWidthStyle(width)` continues to apply on medium+ screens. Fixes Expensify#93969 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-native-web translates FlatList's `pagingEnabled` to CSS scroll-snap, but it only applies `scroll-snap-align: start` to the scroller's direct children. Our per-page <View> rendered inside `renderItem` is not a direct child of the FlatList's internal scroll container, so the carousel ended up with a single snap point. A fast fling on mWeb/desktop Chrome could coast past page 2 of 3 onto the last page. Add `scrollSnapAlign: 'start'` to each page wrapper via a web-gated style so every page becomes a snap point. Add `disableIntervalMomentum`, `snapToInterval`, and `decelerationRate="fast"` to the FlatList as defense in depth on native — no-ops on web, but they arrest momentum precisely at each page boundary on iOS/Android. Fixes Expensify#93970 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`AIFeaturesPromoGuard` had no copilot/delegate awareness. When the host user switched into another account via Copilot, `clearOnyxForDelegateTransition` wiped Onyx using `KEYS_TO_PRESERVE_DELEGATE_ACCESS` — which does NOT preserve `NVP_DISMISSED_PRODUCT_TRAINING`. The guard then saw the dismissed NVP as empty and re-prompted the host user's one-time AI promo for every copilot transition. Subscribe to `ONYXKEYS.ACCOUNT`, track `isActingAsDelegate` via `isActingAsDelegateSelector`, and gate `isEligibleToShowAIFeaturesPromoModal` on `!isActingAsDelegate`. Mirrors the same short-circuit in `ProductTrainingContext`. Fixes Expensify#93973 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`shouldBlockWhileModalActive` previously blocked every navigation action except DISMISS_MODAL and GO_BACK while the AI promo modal was on top. External top-level intents — native share targeting SHARE_MODAL_NAVIGATOR, report deep-links arriving as RESET via getAdaptedStateFromPath — were silently dropped, stranding the user on the modal until they manually closed it. The block's intent is only to prevent an in-app tab swipe from racing the modal overlay. Switch the logic accordingly: - Always allow DISMISS_MODAL / GO_BACK. - Always allow RESET (deep links / share intents). - For NAVIGATE / PUSH / REPLACE, only block when `payload.name` targets the AI promo navigator itself; targets pointing at any sibling navigator/screen proceed and the new screen overlays or replaces the modal naturally. Fixes Expensify#93976 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Explanation of Change
Second attempt to add promo modal for new AI features:
Fixed Issues
$ #90820
$ #93964
$ #93968
$ #93969
$ #93970
$ #93973
$ #93976
$ #93985
PROPOSAL:
Tests
BackandNextbuttons navigate through the pagesLet's go/x/tapping outside dismisses the modalOffline tests
NA
QA Steps
Same as Tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Screen.Recording.2026-06-15.at.12.43.07.mov
Android: mWeb Chrome
Screen.Recording.2026-06-15.at.12.44.06.mov
iOS: Native
Screen.Recording.2026-06-15.at.12.46.58.mov
iOS: mWeb Safari
Screen.Recording.2026-06-15.at.12.53.25.mov
MacOS: Chrome / Safari
Light mode
Screen.Recording.2026-06-08.at.16.16.11.mov
Dark mode
Screen.Recording.2026-06-08.at.17.21.25.mov
Don't intervene onboarding modal
Screen.Recording.2026-06-08.at.17.21.09.mov