Add Getting started checklist for new Submit workspaces - #99842
Conversation
Co-authored-by: Hans Vu <hungvu193@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Android test run: ❌ failThe The PR body has no filled-in
Root causeRead directly from the device's Onyx store for the active policy:
Every other gate passes, confirmed from Onyx: Minor: subtext copy mismatchThe spec asks for linkPersonalCardSubText: 'Import expenses automatically',
Evidence (2)Workspaces list showing the auto-created "Melvin Tester's Workspace" of type Submit
Home screen going straight from "For you" to "Recently added" with no Getting started section
Evidence (1)Recordings- [▶ Session recording of the verification run](https://melvinbot-assets.exops.io/session-33320752010-1-992bc7a7-85c5-4417-b1f2-3e20a16a8357.mp4) |
Web test run:
|
| Step | Status | Observation |
|---|---|---|
| 1. Submit workspace auto-created after Submit-intent onboarding | Onboarding never ran. Workspaces tab reads "No workspaces yet" with only a "New workspace" button; no policy exists for the account. | |
| 2. Home shows a "Getting started" section | Not present. Home snapshot shows only Time sensitive / Recently added / Discover. Expected — the intent gate returns emptyResult when intent is undefined. |
|
| 3. Row "Customize your expense categories" + subtext | Parent section never renders. Static check only: en.ts:1197-1198 defines exactly the spec copy. |
|
| 4. Row "Link personal card" + subtext | Parent section never renders. Static check only — see copy note below. | |
| 5. Exactly two rows, no "Create a workspace" row | No rows observable. | |
| 6. Both rows render unchecked for a brand-new workspace | No workspace exists to evaluate isComplete against. |
|
| 7. Tapping "Customize your expense categories" → workspace Categories | Row never rendered; no navigation could be driven. | |
| 8. Tapping "Link personal card" → Settings > Wallet | Row never rendered; no navigation could be driven. | |
| 9. No JS console errors | Browser console capture is out of scope for the web driver. No crash; navigation across Home, Workspaces, Agents behaved normally. |
The Android finding still stands and is the review question
The Android run on this same commit did reach the state and found the section never renders: the auto-created Submit workspace gives its owner role: "editor", so isPolicyAdmin at useGettingStartedItems.ts:134 returns emptyResult before reaching the isSubmitIntent branch on line 140.
That gate is platform-independent — isPolicyAdmin resolves through getPolicyRole in shared code with no platform branching, so web would hit it identically once the intent is set. I could not confirm that on web, but there is no mechanism by which it would differ.
Minor: subtext copy mismatch
The spec asks for Import expenses automatically. but en.ts:1202 has no trailing period, while customizeExpenseCategoriesSubText on line 1198 does. The string is pre-existing and shared with the TRACK_PERSONAL intent, so changing it affects that intent too.
Evidence (2)
Home screen with no Getting started section (only Time sensitive, Recently added, Discover)
Workspaces tab showing 'No workspaces yet' - no auto-created Submit workspace
|
@MelvinBot Generate the QA steps |
QA steps
I've left the PR body's Setup
Section renders with the right two to-dos
Navigation
Auto-checking
Categories feature toggle
60-day window
Regression checks
Localization
All steps
One copy note before QA runs thisStep 5's expected subtext follows the code, not the spec. #99441 asks for Evidence (1)Recordings- [▶ Session recording of the verification run](https://melvinbot-assets.exops.io/session-33355197736-1-811657b7-75a1-4217-9594-993d80c9d0dd.webm) |
|
@MelvinBot The test steps doesn't work, there's no Getting Started in Home |
… role Submit workspaces use a flat role model, so the creator gets the editor role and isPolicyAdmin was never true for them. The section therefore never rendered. Gate the Submit intent on isPolicyOwner instead, which is true for the auto-created workspace and false for invited members. The unit tests built the Submit fixture as an admin policy, which cannot occur in reality, so they passed while the app failed. The fixture now mirrors production: editor role, owned by the current user. Co-authored-by: Hans Vu <hungvu193@users.noreply.github.com>
|
You're right, and thanks for catching it — I've pushed a fix in 54896c9. Root cause: Submit workspaces use a flat role model. My original comment ("Invited members aren't admins, so the isPolicyAdmin gate filters them out") was simply wrong — that gate filtered out everyone. The fix: gate the Submit intent on ownership instead, which is true for the auto-created workspace and false for someone invited to another person's: if (isSubmitIntent ? !isPolicyOwner(policy, currentUserAccountID) : !isPolicyAdmin(policy)) {
return emptyResult;
}The other intents keep Why the unit tests didn't catch thisThey built the Submit fixture as
|
|
No code fix is needed — every automated check on this PR passes. The one red check just needs a human reviewer.
To turn it green: an Expensify employee other than the PR author needs to approve. Nothing I can push will change that. Full check status as of the latest commit (
|
|
@MelvinBot you forgot to add locale installHook.js:1 Error: homePage.gettingStartedSection.customizeExpenseCategories was not found in the en locale |
|
The key is there — I think you hit a stale bundler cache. Try clearing
I also confirmed it is in the JS chunk a dev server built from the current head (54896c9) actually serves: Why a stale cache produces exactly this error
Web test run:
|
| Step | Status | Observation |
|---|---|---|
| 1. No "was not found in the en locale" error on a freshly-built bundle | ✅ pass | Dev server built from 54896c9; served en chunk contains the key. ENVIRONMENT=development, so a missing key throws — the failure mode was armed and never fired. The Search router calls translate('homePage.gettingStartedSection.createExpense') unconditionally (useCreateNavigationSuggestions.ts:100) and rendered clean, proving the subtree resolves. |
| 2. Home shows the "Getting started" section | ⛔ blocked | Not a product failure. CI runs with SKIP_ONBOARDING=true, so NVP_INTRO_SELECTED.choice is never set and the hook returns emptyResult. Needs SKIP_ONBOARDING=false plus a fresh Submit-intent sign-up, which this CI path can't do. |
| 3. "Customize your expense categories" renders its real label | ⛔ blocked | Row never mounts (same cause). Bundle-level evidence only: the served chunk has the string verbatim. |
| 4. "Link personal card" renders its real label | ⛔ blocked | Row never mounts (same cause). Served chunk contains linkPersonalCard and linkPersonalCardSubText. |
| 5. Verbatim console errors | The web harness can't read the browser console. Proxy signals: no error boundary or crash screen at any point, and a 200-entry network dump showed zero non-2xx responses for any app request or JS chunk. |
What I did not verify: the two rows actually rendering. That still needs a Submit-intent account. If the error persists for you after a clean rebuild, tell me and I'll dig further — a reproduction on your machine would mean my stale-cache read is wrong.
Evidence (2)
Home loaded cleanly on a fresh build of 54896c9 with no error boundary or crash screen
Search router rendered, proving homePage.gettingStartedSection.* translate calls resolve without throwing
|
@MelvinBot Update the test steps to your checklist, check all the checkboxes.. Add the video below to your screenshots section: Screen.Recording.2026-08-31.at.13.32.28.mov |
|
Done — PR description updated. What changed:
Two things to confirm, since I'm attesting on your behalf:
|
|
@MelvinBot Mark this PR as ready for review |
|
@abzokhattab 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] |
|
Marked this PR as ready for review. All CI checks are green. The only non-passing check is
|
| } | ||
|
|
||
| if (!isPolicyAdmin(policy)) { | ||
| // Submit workspaces use a flat role model — `getRoleForCallerOnNewPolicy` hands even the creator the `editor` role, and |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
This comment uses an em dash, which the plain-comment-style rule flags. Comments should read as plain sentences without em dashes.
Rewrite the em dash as separate clauses or sentences, e.g.:
// Submit workspaces use a flat role model. `getRoleForCallerOnNewPolicy` hands even the creator the `editor` role, and
// `updateWorkspaceMembersRole` refuses to change it, so `isPolicyAdmin` is never true for them. Gate the Submit intent on
// ownership instead: true for the workspace auto-created during onboarding, false for members invited to someone else's.Reviewed at: 54896c9 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed in 9c54d02 — rewrote the em dash as a sentence break.
|
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai. |
Second web run:
|
| Step | Status | Observation |
|---|---|---|
| 1. Sign up for a brand-new account | ✅ pass | Fresh account minted by hand; dev env auto-authenticated |
| 2. Choose "Submit expenses to my employer" | ✅ pass | Intent selected, advanced to name-entry step |
| 3. Submit workspace auto-created, user is owner | ✅ pass | Workspaces row: "Melvin Bot's Workspace, Default, Owner: Melvin Bot, Workspace type: Submit" |
| 4. Navigate to Home | ✅ pass | Home heading rendered |
5. Getting started between For you and Recently added |
✅ pass | Narrow layout order: Time sensitive → For you → Getting started → Recently added → Spend over time → Discover |
6. Exactly two rows, no Create a workspace row |
✅ pass | Labels and subtext byte-match src/languages/en.ts; no Create a workspace row |
| 7. Both rows unchecked | ✅ pass | Both checkboxes report unselected pre-interaction — but see caveat above |
| 8. Categories row → workspace Categories page | ✅ pass | Landed on Categories heading with Add category and the category list |
9. Link personal card → Settings > Wallet |
✅ pass | Wallet page with Bank accounts / Cards / Add personal card |
| 10. Row checks only after adding a custom category | ❌ fail | Row checked with Done badge after only opening and leaving the Categories page |
| 11. Link a card, row checks, section hides | ⛔ blocked | Plaid "Mock Bank" flow navigated the tab to an unreachable host (DNS_PROBE_FINISHED_NXDOMAIN), ending the session. Environment issue, not a PR defect |
| 12. Invited member does not see the section | ⛔ blocked | Needs a second concurrent account — unsupported in this harness |
| 13. Turning Categories off/on hides/restores that row | ⛔ blocked | Not reached; session had already died at step 11 |
| 14. Other intents unaffected | ⛔ blocked | Needs a separate sign-up loop — unsupported in this harness |
| 15. No JS console errors | ⛔ blocked | No console capture available on web; no visible error banners over the reachable portion |
Suggested fix: stop inferring "customized" by diffing names against a hardcoded list. Compare against the categories the workspace was actually seeded with, or track an explicit signal that the user edited categories. Either way the row also needs its category data present before first render, or it will keep flipping from unchecked to checked on its own.
Steps 12 and 14 need two accounts and still want a manual pass before merge.
Evidence (5)
Home ordering: For you, Getting started (both rows unchecked), Recently added
Workspace Categories page reached from the Customize your expense categories row
Bug evidence: row checked with Done badge after only opening the Categories page
Settings > Wallet reached from the Link personal card row
Unreachable host error that ended the session during the Link personal card flow
garrettmknight
left a comment
There was a problem hiding this comment.
Looks good from a product perspective.
@abzokhattab Yeah, please go ahead and review it. I assigned you to the PR, so you can also ask Melvin to push code or fix bug. |
|
BUG
Expected: Actual: The row is already checked / shows Done. Screen.Recording.2026-09-02.at.20.16.59.mov |
|
@MelvinBot cover the valid issues |
…ce fallback - Anchor the 60-day Getting started window for the Submit intent on the active Submit workspace's own created timestamp, instead of the account-level nvp_private_firstPolicyCreatedDate, which points at an older workspace the user may have deleted and would hand the new workspace an expired window. - Hide the section for the Submit intent when there is no Submit workspace, rather than offering a Create a workspace step that creates a paid workspace and makes the section disappear when completed. - Drop the trailing period from customizeExpenseCategoriesSubText so both rows in the list punctuate the same way. Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 717a09c6..cd357f28 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1079,7 +1079,7 @@ const translations: TranslationDeepObject<typeof en> = {
customizeSpendCategories: 'Ausgabenkategorien anpassen',
customizeSpendCategoriesSubText: 'Ausgaben organisieren und klassifizieren',
customizeExpenseCategories: 'Passen Sie Ihre Ausgabenkategorien an',
- customizeExpenseCategoriesSubText: 'Fügen Sie die Kategorien Ihres Unternehmens hinzu, um Ihre Ausgaben zu kodieren',
+ customizeExpenseCategoriesSubText: 'Fügen Sie die Kategorien Ihres Unternehmens hinzu, um Ihre Ausgaben zu codieren',
createExpense: 'Ausgabe erstellen',
createExpenseSubText: 'Scannen Sie eine Ausgabe, ziehen Sie sie per Drag & Drop herein oder geben Sie sie manuell über die + Schaltfläche ein',
linkPersonalCard: 'Persönliche Karte verknüpfen',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 24931abc..b3b09ee2 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1085,7 +1085,7 @@ const translations: TranslationDeepObject<typeof en> = {
customizeSpendCategories: 'Personaliza las categorías de gasto',
customizeSpendCategoriesSubText: 'Organiza y clasifica gastos',
customizeExpenseCategories: 'Personaliza tus categorías de gastos',
- customizeExpenseCategoriesSubText: 'Añade las categorías de tu empresa para codificar tus gastos',
+ customizeExpenseCategoriesSubText: 'Añade las categorías de tu empresa para clasificar tus gastos',
createExpense: 'Crear un gasto',
createExpenseSubText: 'Escanea, arrastra y suelta o introduce manualmente un gasto usando el botón +',
linkPersonalCard: 'Vincular tarjeta personal',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 5c3491d8..f1487a31 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1095,7 +1095,7 @@ const translations: TranslationDeepObject<typeof en> = {
inviteAccountant: 'Invita il tuo commercialista',
customizeSpendCategories: 'Personalizza le categorie di spesa',
customizeSpendCategoriesSubText: 'Organizza e classifica le spese',
- customizeExpenseCategories: 'Personalizza le tue categorie di spesa',
+ customizeExpenseCategories: 'Personalizza le categorie di spesa',
customizeExpenseCategoriesSubText: 'Aggiungi le categorie della tua azienda per codificare le tue spese',
createExpense: 'Crea una spesa',
createExpenseSubText: 'Scansiona, trascina e rilascia oppure inserisci manualmente una spesa usando il pulsante +',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index f3eb99e5..42aee520 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1078,8 +1078,8 @@ const translations: TranslationDeepObject<typeof en> = {
inviteAccountant: '会計士を招待',
customizeSpendCategories: '支出カテゴリをカスタマイズする',
customizeSpendCategoriesSubText: '経費を整理して分類する',
- customizeExpenseCategories: '経費カテゴリをカスタマイズ',
- customizeExpenseCategoriesSubText: '会社のカテゴリを追加して経費を分類しましょう',
+ customizeExpenseCategories: '経費カテゴリをカスタマイズする',
+ customizeExpenseCategoriesSubText: '経費を仕分けできるように、会社のカテゴリを追加してください',
createExpense: '経費を作成',
createExpenseSubText: 'スキャンするか、ドラッグ&ドロップするか、または+ボタンから手入力して経費を追加します',
linkPersonalCard: '個人カードをリンクする',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index abe424fe..f48ae321 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1093,7 +1093,7 @@ const translations: TranslationDeepObject<typeof en> = {
inviteAccountant: 'Nodig je accountant uit',
customizeSpendCategories: 'Uitgavencategorieën aanpassen',
customizeSpendCategoriesSubText: 'Orden en classificeer uitgaven',
- customizeExpenseCategories: 'Pas je uitgavencategorieën aan',
+ customizeExpenseCategories: 'Pas je onkostencategorieën aan',
customizeExpenseCategoriesSubText: 'Voeg de categorieën van je bedrijf toe om je uitgaven te coderen',
createExpense: 'Maak een uitgave',
createExpenseSubText: 'Scan, sleep neer en zet neer, of voer handmatig een uitgave in met de +-knop',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index c5eb31a2..76cd469d 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1110,7 +1110,7 @@ const translations: TranslationDeepObject<typeof en> = {
customizeSpendCategories: 'Dostosuj kategorie wydatków',
customizeSpendCategoriesSubText: 'Porządkuj i klasyfikuj wydatki',
customizeExpenseCategories: 'Dostosuj swoje kategorie wydatków',
- customizeExpenseCategoriesSubText: 'Dodaj kategorie swojej firmy, aby kodować wydatki',
+ customizeExpenseCategoriesSubText: 'Dodaj kategorie swojej firmy, aby kategoryzować wydatki',
createExpense: 'Dodaj wydatek',
createExpenseSubText: 'Zeskanuj, przeciągnij i upuść lub ręcznie wprowadź wydatek za pomocą przycisku +',
linkPersonalCard: 'Połącz kartę osobistą',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index b7758bd3..e272fde7 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1093,8 +1093,8 @@ const translations: TranslationDeepObject<typeof en> = {
inviteAccountant: 'Convide seu contador',
customizeSpendCategories: 'Personalizar categorias de gastos',
customizeSpendCategoriesSubText: 'Organize e classifique despesas',
- customizeExpenseCategories: 'Personalize suas categorias de despesas',
- customizeExpenseCategoriesSubText: 'Adicione as categorias da sua empresa para codificar suas despesas',
+ customizeExpenseCategories: 'Personalize suas categorias de despesa',
+ customizeExpenseCategoriesSubText: 'Adicione as categorias da sua empresa para classificar suas despesas',
createExpense: 'Criar uma despesa',
createExpenseSubText: 'Escaneie, arraste e solte ou insira manualmente uma despesa usando o botão +',
linkPersonalCard: 'Vincular cartão pessoal',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index dcc1cf1e..7139e051 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1053,8 +1053,8 @@ const translations: TranslationDeepObject<typeof en> = {
inviteAccountant: '邀请你的会计',
customizeSpendCategories: '自定义支出类别',
customizeSpendCategoriesSubText: '整理并分类支出',
- customizeExpenseCategories: '自定义您的支出类别',
- customizeExpenseCategoriesSubText: '添加贵公司的类别以对支出进行编码',
+ customizeExpenseCategories: '自定义您的报销类别',
+ customizeExpenseCategoriesSubText: '添加你们公司的类别以归类报销费用',
createExpense: '创建报销费用',
createExpenseSubText: '使用 + 按钮扫描、拖放或手动输入支出',
linkPersonalCard: '关联个人银行卡',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
Pushed What I fixed1. The 60-day window was anchored on the wrong workspace (Codex P2, and item 2 of my own review) The window keyed off It now anchors on the Submit workspace's own 2. The A Submit-intent user with no Submit workspace got a lone The section now hides for the Submit intent whenever there is no usable Submit workspace. This folded in the separate Team/Corporate gate, so the guard order is simpler than before. 3. Copy nit — dropped the trailing period from Your bug: the category row checks itselfConfirmed, and the root cause is outside this PR.
Two things follow:
Two review items I did not change
Verification
|
|
melvin created an issue on its own here #100220 after my comment here #99842 (comment) iam confused: should we tackle the mentioned issue as a part of the current PR or should we address it there |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeMacOS: Chrome / SafariScreen.Recording.2026-09-02.at.20.12.09.mov |
abzokhattab
left a comment
There was a problem hiding this comment.
If we decide to exclude the bug above to be fixed in the created issue then the current changes looks good to me
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #99441 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
I saw that Melvin mentioned that will require BE changes that's why it created a new issue. I'm fine with that |
|
🚧 iwiznia has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/iwiznia in version: 9.4.70-0 🚀
|
|
Yes — help site changes were required. Draft PR: #100340 One article documents this feature and two of its statements were made wrong by this PR:
The article also never documented the two to-dos, that the categories item depends on Categories being enabled, or that invited members of someone else's Submit workspace are filtered out by the ownership gate. What the docs PR changes, and how each claim was verifiedChanges to
Verified against source rather than the PR description:
Not changed: no other file under Not run: The draft PR is labeled @MelvinBot, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
🚀 Deployed to staging by https://github.com/iwiznia in version: 9.4.72-0 🚀
|






Explanation of Change
Extends the existing
Getting startedslot on Home to serve the "Submit expenses to my employer" onboarding intent, with two setup to-dos tailored to a new Submit workspace.Adding the intent to the allow-list in
useGettingStartedItemswas not sufficient on its own — three gates further down would still have rejected these users:The paid-plan gate.
isPaidGroupPolicyonly acceptsTEAM/CORPORATE, butuseAutoCreateSubmitWorkspacecreates the workspace asCONST.POLICY.TYPE.SUBMIT. Left alone, the section would have shown a lone "Create a workspace" step to a user who already has one. The Submit intent now checksisGroupPolicy(paid or submit) instead; the other intents are untouched.The 60-day window.
isWithinGettingStartedPeriodkeys offnvp_private_firstDayFreeTrial, which is only written once a paid trial starts (seeupgradeSubmitinPolicy.ts). Submit is a free plan, so that NVP is absent and the section would never have rendered at all. The Submit intent now falls back tonvp_private_firstPolicyCreatedDatewhen there is no trial date. If a Submit user later upgrades, the trial date takes precedence, so the window is anchored consistently for the whole account lifetime.The admin gate. Submit workspaces use a flat role model, so the creator of the auto-created workspace gets the
editorrole andisPolicyAdminwas never true for them — the section never rendered on device. The Submit intent is now gated onisPolicyOwnerinstead, which is true for the auto-created workspace and false for invited members. The original unit tests built the Submit fixture as an admin policy, a state that cannot occur in reality, so they passed while the app failed; the fixture now mirrors production (editor role, owned by the current user).The two to-dos:
Customize your expense categorieshasCustomCategories)/workspaces/:policyID/categoriesLink personal card/settings/walletBoth completion checks and both routes already existed and were already read by this hook, so no new helpers or Onyx keys were needed. The
linkPersonalCardcopy already matched the spec verbatim and is reused;customizeExpenseCategories/customizeExpenseCategoriesSubTextare new keys added across all 11 locales.Intent constants: gated on
EMPLOYER(what the "Submit expenses to my employer" answer writes) andSUBMIT(the variant written for users invited to someone else's workspace). Invited members do not own the workspace, so theisPolicyOwnergate filters them out without extra logic.No
Create a workspacestep is shown for this intent, unlike the others — the Submit workspace is auto-created during onboarding, so the row would land pre-checked and add nothing. The issue specifies exactly two to-dos.Per the existing
buildResultbehaviour, the whole slot hides once both to-dos are complete, and per the shared gate it hides after 60 days.Open questions for the reviewer
nvp_private_firstPolicyCreatedDatebecause it is already in Onyx, is a date string in the same format, and is the closest available proxy for "when this user started". If Product intends a different anchor (account creation, onboarding completion), that is a one-line change in the gate. Flagged forgarrettmknight.policyCategories_<policyID>prefetch. Nothing on Home dispatchesOpenPolicyCategoriesPage, so the categories collection is only populated by whateverOpenApp/ReconnectAppreturns. If it is absent for a user who has added categories, the row would render unchecked. This is pre-existing behaviour shared with the other three intents, not introduced here, but worth a look during review.getBankLinkedPersonalCardsignoreslastScrapeResult, so a personal card whose connection later breaks still reads as done. This matches the existingTRACK_PERSONALbehaviour; called out so the choice is deliberate rather than inherited by accident.Fixed Issues
$ #99441
PROPOSAL: #99441 (comment)
AI Tests
Automated checks run locally by MelvinBot on this branch. These do not substitute for the human
TestsandQA Stepssections below.npm run typechecknpm run lint-changednpm test -- tests/unit/hooks/useGettingStartedItems.test.tsnpm test -- tests/unit/pages/home/GettingStartedSectionnpm test -- TranslateTest TranslatorTest useCreateNavigationSuggestionsTestnpm run react-compiler-compliance-check check <hook>npm run spell-changed54896c9npm testsuitescripts/generateTranslations.ts --dry-runts-noderesolution failure;buninvocation blocked by the sandbox). All 10 non-English locales were written by hand.Android native run (pre-fix) is what caught the real bug. Driving a fresh sign-up through the "Submit expenses to my employer" onboarding flow on the Android emulator confirmed the Submit workspace is auto-created (
type: "submit2026", set as active policy) and that every other gate passed — but theGetting startedsection did not render. Reading the device's Onyx store showed the workspace owner hasrole: "editor", notadmin, so theisPolicyAdmingate returned early. That is the bug fixed in54896c9.Web runtime verification is not reachable from CI. The CI web session runs with
SKIP_ONBOARDING=true, so a fresh account skips the onboarding purpose screen entirely, no intent is ever written tonvp_introSelected, no Submit workspace is auto-created, and the hook returnsemptyResultat its first gate.OnboardingGuardalso redirects any manual navigation into the onboarding flow back to Home while that flag is set, so the state cannot be reached by hand. Exercising this feature on web requires a session started withSKIP_ONBOARDING=false. The reviewer's recording inScreenshots/Videoscovers this gap.Tests
Setup note: the dev environment must run with
SKIP_ONBOARDING=false, otherwise the onboarding purpose screen is skipped, no intent is written, and the section is gated off by design.Getting startedsection appears between theFor youandRecently addedsections.Create a workspacerow:Customize your expense categories— subtextAdd your company’s categories to code your expenses.Link personal card— subtextImport expenses automaticallyCustomize your expense categoriesrow and verify it navigates to the workspace Categories page (/workspaces/:policyID/categories).Link personal cardrow and verify it navigates to Settings > Wallet (/settings/wallet).Customize your expense categoriesrow is now checked.Link personal cardrow is now checked and — with both to-dos complete — the wholeGetting startedsection is hidden.SUBMITintent is written. Verify theGetting startedsection does not appear, because that user does not own the workspace.More featuresmenu. Return to Home and verify only theLink personal cardrow remains; turn Categories back on and verify the row returns.Manage my team's expensesand verify that intent's existingGetting startedsteps are unchanged.Offline tests
Getting startedsection visible.Getting startedsection still renders from cached Onyx data, with the same two rows and the same checkbox states as when online (this section is read-only and derives entirely from Onyx, so it makes no API calls of its own).Customize your expense categoriesrow and verify it still navigates to the workspace Categories page, which shows its usual offline state.Link personal cardrow, and verify it still navigates to Settings > Wallet, which shows its usual offline state.QA Steps
Same as tests (steps 1–15 above). Staging runs the real onboarding flow, so the
SKIP_ONBOARDINGsetup note does not apply there.PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-08-31.at.13.32.28.mov