You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for Payment 2026-05-24] label:inbox Extend useDocumentTitle hook to remaining pages across the app #83378
Version Number: N/A Reproducible in staging?: N/A Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack: N/A Email or phone of affected tester (no customers): N/A Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: N/A Issue reported by:@yuwenmemon Slack conversation: N/A
Action Performed:
Open any page outside of Settings (e.g. a report, workspace page, search, etc.)
Check the browser tab title
Expected Result:
Every page in the app should have a descriptive <title> that reflects its content (e.g. "Settings - Profile", "Report - #123", "Search Results").
Actual Result:
Only Settings pages have descriptive titles (added in #81150). All other pages still show the generic "New Expensify" title.
Workaround:
N/A — cosmetic/usability issue only.
Platforms:
Android: App
Android: mWeb Chrome
iOS: App
iOS: mWeb Safari
iOS: mWeb Chrome
Windows: Chrome
MacOS: Chrome / Safari
Context
PR #81150 (fixing #74767) introduced a useDocumentTitle hook and applied it to Settings pages. This follow-up covers extending it to all remaining pages so every route has a descriptive browser tab title.
Please re-state the problem that we are trying to solve in this issue.
Only Settings pages have descriptive browser tab titles (via the useDocumentTitle hook added in #81150). All other pages — Home, Inbox/Reports, Search, Workspaces, Domain, Onboarding, etc. — show the generic "New Expensify" title in the browser tab, providing no context about the current page.
What is the root cause of that problem?
React Navigation's built-in documentTitle is explicitly disabled in NavigationRoot.tsx:260-262. The app uses a custom title system via setPageTitle() in src/libs/UnreadIndicatorUpdater/updateUnread/index.ts, which composes a page-specific title with an unread message count. However, the useDocumentTitle hook that feeds this system was only added to 9 Settings pages — no other pages call it, so currentPageTitle remains empty and document.title falls back to CONFIG.SITE_TITLE ("New Expensify").
What changes do you think we should make in order to solve the problem?
Recommended approach: Centralized title setting in NavigationRoot.handleStateChange
The parseAndLogRoute function in src/libs/Navigation/NavigationRoot.tsx:52-87 already calls findFocusedRoute(state) on every navigation state change and gets the focused screen name. This is the ideal injection point:
Create a screen-to-title mapping (e.g., src/libs/Navigation/screenTitles.ts) that maps screen names from SCREENS.ts to translation key paths:
Call setPageTitle() from parseAndLogRoute after resolving the focused route's screen name to a translated title. For static titles, look up the mapping and translate. For dynamic titles (like the report name), the individual page can continue to use useDocumentTitle as an override — since setPageTitle() is last-writer-wins, the page-level hook call will take precedence over the navigation-level call.
Keep existing useDocumentTitle calls in Settings pages as-is (or optionally migrate them to the mapping). Pages needing dynamic titles (e.g., ReportScreen showing the chat name) should continue using the hook directly.
Tier 2 (workspace central pane — 17 screens): WorkspaceOverviewPage, WorkspaceMembersPage, WorkspaceWorkflowsPage, etc. (all defined in WorkspaceSplitNavigator.tsx:19-38)
What alternative solutions did you explore? (Optional)
Per-page hook approach — Add useDocumentTitle() to each of the 40+ remaining page components individually. This is consistent with the existing pattern but requires touching many files and is easy to miss for new screens added in the future.
Re-enable React Navigation's documentTitle with custom formatter — The NavigationContainer supports documentTitle={{ formatter: (options, route) => ... }}. However, this bypasses the unread count system (updateUnread) and would need non-trivial integration to work correctly.
screenListeners on the root navigator — Adding a focus listener at the RootStack.Navigator level. This only fires for top-level routes, not deeply nested screens within split navigators, so it wouldn't cover workspace/settings/domain central pane screens.
The centralized parseAndLogRoute approach is recommended because it fires on every navigation change regardless of nesting depth, requires no modification to existing page components, and naturally integrates with the existing setPageTitle()/unread count system.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this analysis.
@MelvinBot Can you draft a plan using the simpler per-page hook approach (Conventions established in #81150)? It's more files to touch, but it's explicit, easy to review per-page, and doesn't introduce a second title-setting mechanism. The centralized approach is clever but the hybrid result is harder to reason about.
Per your request, here's a plan using the per-page hook approach established in #81150 — no centralized mechanism, just explicit useDocumentTitle calls in every page component.
importuseDocumentTitlefrom'@hooks/useDocumentTitle';importuseLocalizefrom'@hooks/useLocalize';// already imported in most pages// Inside component body, after other hooks:useDocumentTitle(`${translate('sectionName')} - ${translate('pageName')}`);
Nested pages (Settings, Workspace, Domain): "Section - Page" format
Top-level pages (Home, Search, Workspaces list): Just translate('pageName') (no prefix)
Dynamic pages (Report, Domain initial): Use runtime data (report name, domain name)
Batch 1: Top-Level Tab Pages (4 files)
File
Title
Call
src/pages/home/HomePage.tsx
"Home"
useDocumentTitle(translate('common.home'))
src/pages/Search/SearchPage.tsx
"Search"
useDocumentTitle(translate('common.search'))
src/pages/workspace/WorkspacesListPage.tsx
"Workspaces"
useDocumentTitle(translate('common.workspaces'))
src/pages/inbox/ReportScreen.tsx
(dynamic)
useDocumentTitle(reportName || '') — use the report's display name so the browser tab shows which chat is open
Batch 2: Workspace Central Pane (18 files)
All use the format: useDocumentTitle(`${translate('workspace.common.workspace')} - ${translate('...')}`)
ReportScreen dynamic title — Should it show the report display name (e.g., "Chat with Alice") or just a static "Inbox"? Dynamic is more useful but the report data loads asynchronously.
Domain page prefix — Should domain pages use "Domain - Admins" format (requires adding a translation key) or just "Admins"?
Onboarding priority — These are modal screens seen only once. Should they be included in the initial pass or deferred?
Batch strategy — Should this be one large PR or split into multiple PRs per batch? Multiple PRs are easier to review but add overhead.
Total Scope
Batch
Files
Complexity
Top-level tabs
4
Low (1 dynamic)
Workspace pages
18
Low (all static, 1 needs useLocalize added)
Domain pages
5
Low (1 dynamic)
Onboarding pages
13
Low (all static)
Total
40 files
Each change is ~2 lines (1 import + 1 hook call)
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this plan.
@MelvinBot, I would skip the titles for the Onboarding Modal pages. Also, for the domain pages, I would update the domain to match the page prefix (i.e., "example.com - SAML"). Then, for the Workspace pages, I would replace "Workspace" with the actual name of the workspace itself (i.e. "Expensify Inc. - Tags")
Automation appears to have failed here, updated. @mananjadhav plz complete the BZ checklist and tag me in a post once you have. Thx
oh...hmm... realizing it mighta failed cuz @mananjadhav wasn't assigned.
📣 @Ilya0527! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to the comment on the PR: New Feature extended to all the pages.
Link to the comment on the Issue:
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: NA
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Activity
PiyushChandra17 commentedon Feb 24, 2026
Hi @yuwenmemon Is this open for external contributors? If so, I'd like to work on this (the details are already specified in OP)
MelvinBot commentedon Feb 24, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Only Settings pages have descriptive browser tab titles (via the
useDocumentTitlehook added in #81150). All other pages — Home, Inbox/Reports, Search, Workspaces, Domain, Onboarding, etc. — show the generic "New Expensify" title in the browser tab, providing no context about the current page.What is the root cause of that problem?
React Navigation's built-in
documentTitleis explicitly disabled inNavigationRoot.tsx:260-262. The app uses a custom title system viasetPageTitle()insrc/libs/UnreadIndicatorUpdater/updateUnread/index.ts, which composes a page-specific title with an unread message count. However, theuseDocumentTitlehook that feeds this system was only added to 9 Settings pages — no other pages call it, socurrentPageTitleremains empty anddocument.titlefalls back toCONFIG.SITE_TITLE("New Expensify").What changes do you think we should make in order to solve the problem?
Recommended approach: Centralized title setting in
NavigationRoot.handleStateChangeThe
parseAndLogRoutefunction insrc/libs/Navigation/NavigationRoot.tsx:52-87already callsfindFocusedRoute(state)on every navigation state change and gets the focused screen name. This is the ideal injection point:Create a screen-to-title mapping (e.g.,
src/libs/Navigation/screenTitles.ts) that maps screen names fromSCREENS.tsto translation key paths:Call
setPageTitle()fromparseAndLogRouteafter resolving the focused route's screen name to a translated title. For static titles, look up the mapping and translate. For dynamic titles (like the report name), the individual page can continue to useuseDocumentTitleas an override — sincesetPageTitle()is last-writer-wins, the page-level hook call will take precedence over the navigation-level call.Keep existing
useDocumentTitlecalls in Settings pages as-is (or optionally migrate them to the mapping). Pages needing dynamic titles (e.g., ReportScreen showing the chat name) should continue using the hook directly.Pages needing coverage (prioritized):
WorkspaceSplitNavigator.tsx:19-38)What alternative solutions did you explore? (Optional)
Per-page hook approach — Add
useDocumentTitle()to each of the 40+ remaining page components individually. This is consistent with the existing pattern but requires touching many files and is easy to miss for new screens added in the future.Re-enable React Navigation's
documentTitlewith customformatter— TheNavigationContainersupportsdocumentTitle={{ formatter: (options, route) => ... }}. However, this bypasses the unread count system (updateUnread) and would need non-trivial integration to work correctly.screenListenerson the root navigator — Adding afocuslistener at theRootStack.Navigatorlevel. This only fires for top-level routes, not deeply nested screens within split navigators, so it wouldn't cover workspace/settings/domain central pane screens.The centralized
parseAndLogRouteapproach is recommended because it fires on every navigation change regardless of nesting depth, requires no modification to existing page components, and naturally integrates with the existingsetPageTitle()/unread count system.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR, or@MelvinBot <your feedback>to refine this analysis.yuwenmemon commentedon Feb 24, 2026
@MelvinBot Can you draft a plan using the simpler per-page hook approach (Conventions established in #81150)? It's more files to touch, but it's explicit, easy to review per-page, and doesn't introduce a second title-setting mechanism. The centralized approach is clever but the hybrid result is harder to reason about.
MelvinBot commentedon Feb 24, 2026
Per-Page
useDocumentTitleImplementation PlanPer your request, here's a plan using the per-page hook approach established in #81150 — no centralized mechanism, just explicit
useDocumentTitlecalls in every page component.Convention (from PR #81150)
Every page adds two imports and one hook call:
"Section - Page"formattranslate('pageName')(no prefix)Batch 1: Top-Level Tab Pages (4 files)
src/pages/home/HomePage.tsxuseDocumentTitle(translate('common.home'))src/pages/Search/SearchPage.tsxuseDocumentTitle(translate('common.search'))src/pages/workspace/WorkspacesListPage.tsxuseDocumentTitle(translate('common.workspaces'))src/pages/inbox/ReportScreen.tsxuseDocumentTitle(reportName || '')— use the report's display name so the browser tab shows which chat is openBatch 2: Workspace Central Pane (18 files)
All use the format:
useDocumentTitle(`${translate('workspace.common.workspace')} - ${translate('...')}`)src/pages/workspace/WorkspaceOverviewPage.tsxworkspace.common.profilesrc/pages/workspace/workflows/WorkspaceWorkflowsPage.tsxworkspace.common.workflowssrc/pages/workspace/invoices/WorkspaceInvoicesPage.tsxworkspace.common.invoicessrc/pages/workspace/WorkspaceMembersPage.tsxworkspace.common.memberssrc/pages/workspace/accounting/PolicyAccountingPage.tsxworkspace.common.accountingsrc/pages/workspace/categories/WorkspaceCategoriesPage.tsxworkspace.common.categoriessrc/pages/workspace/WorkspaceMoreFeaturesPage.tsxworkspace.common.moreFeaturessrc/pages/workspace/tags/WorkspaceTagsPage.tsxworkspace.common.tagssrc/pages/workspace/taxes/WorkspaceTaxesPage.tsxworkspace.common.taxessrc/pages/workspace/reports/WorkspaceReportsPage.tsxcommon.reportssrc/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsxworkspace.common.expensifyCardsrc/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsxworkspace.common.companyCardssrc/pages/workspace/perDiem/WorkspacePerDiemPage.tsxcommon.perDiemsrc/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsxworkspace.common.receiptPartnerssrc/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsxworkspace.common.distanceRatessrc/pages/workspace/travel/PolicyTravelPage.tsxworkspace.moreFeatures.travel.titlesrc/pages/workspace/rules/PolicyRulesPage.tsxworkspace.common.rulessrc/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsxworkspace.moreFeatures.timeTracking.titleNote:
WorkspaceExpensifyCardPage.tsxis the only file that doesn't already importuseLocalize— it will need that import added.Batch 3: Domain Central Pane (5 files)
src/pages/domain/DomainInitialPage.tsxdomainNamevariable (e.g., "expensify.com")src/pages/domain/DomainSamlPage.tsxdomain.samlsrc/pages/domain/Admins/DomainAdminsPage.tsxdomain.admins.titlesrc/pages/domain/Members/DomainMembersPage.tsxdomain.members.titlesrc/pages/domain/Groups/DomainGroupsPage.tsxdomain.groups.titleDomain pages don't have a natural parent label like "Settings" or "Workspace". Options:
"SAML","Admins")"Domain - SAML") — but there's notranslate('common.domain')key currentlyBatch 4: Onboarding Modal (13 files)
These are shown in a modal during first-time setup. Each title uses the page heading text.
src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsxonboarding.purpose.titlesrc/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsxonboarding.whatsYourNamesrc/pages/OnboardingWorkEmail/BaseOnboardingWorkEmail.tsxonboarding.workEmail.titlesrc/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsxonboarding.workEmailValidation.titlesrc/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsxonboarding.peopleYouMayKnowsrc/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsxonboarding.joinAWorkspacesrc/pages/OnboardingEmployees/BaseOnboardingEmployees.tsxonboarding.employees.titlesrc/pages/OnboardingAccounting/BaseOnboardingAccounting.tsxonboarding.accounting.titlesrc/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsxonboarding.interestedFeatures.titlesrc/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsxonboarding.workspace.titlesrc/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsxonboarding.confirmWorkspace.titlesrc/pages/OnboardingWorkspaceCurrency/BaseOnboardingWorkspaceCurrency.tsxcommon.currencysrc/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsxonboarding.inviteMembers.titleOpen Questions for the Team
"Domain - Admins"format (requires adding a translation key) or just"Admins"?Total Scope
Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR, or@MelvinBot <your feedback>to refine this plan.yuwenmemon commentedon Feb 24, 2026
@MelvinBot, I would skip the titles for the Onboarding Modal pages. Also, for the domain pages, I would update the domain to match the page prefix (i.e., "example.com - SAML"). Then, for the Workspace pages, I would replace "Workspace" with the actual name of the workspace itself (i.e. "Expensify Inc. - Tags")
melvin-bot commentedon Feb 24, 2026
Triggered auto assignment to @joekaufmanexpensify (
NewFeature), see https://stackoverflowteams.com/c/expensify/questions/14418#:~:text=BugZero%20process%20steps%20for%20feature%20requests for more details. Please add this Feature request to a GH project, as outlined in the SO. Remember to draft any necessary HelpDot updates by following the instructions here.27 remaining items
mallenexpensify commentedon May 18, 2026
Automation appears to have failed here, updated. @mananjadhav plz complete the BZ checklist and tag me in a post once you have. Thx
oh...hmm... realizing it mighta failed cuz @mananjadhav wasn't assigned.
melvin-bot commentedon May 18, 2026
📣 @Ilya0527! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
mananjadhav commentedon May 19, 2026
BugZero Checklist:
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to the comment on the PR: New Feature extended to all the pages.
Link to the comment on the Issue:
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: NA
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue: https://github.com/Expensify/Expensify/issues/639574
Regression Test Proposal
Precondition
Test:
ProfileDo we agree 👍 or 👎
Zapier Logs
Run ID: 00040eee-c449-ae0e-12fe-82ba3e9ecee4mananjadhav commentedon May 19, 2026
@mallenexpensify The checklist completed.
mananjadhav commentedon May 22, 2026
@mallenexpensify I am not sure why the payout date should be 2026-05-24. It should've been 2026-05-18, same as the other issue.
mallenexpensify commentedon May 25, 2026
Shows deployed to production on 5/17. Also, can you address this @mananjadhav
mananjadhav commentedon May 26, 2026
@mallenexpensify This was deployed on March 17. We didn't payout because the follow up issue was pending.
I explained the pricing + regression part here.
mallenexpensify commentedon May 26, 2026
Payment Summary
Contributor+: @mananjadhav due $500 via NewDot
mananjadhav commentedon May 29, 2026
Seems like @joekaufmanexpensify got a C+ status ;)
@mallenexpensify can you correct the payment summary here?
mallenexpensify commentedon May 29, 2026
@mananjadhav updated. GH sux sometimes
garrettmknight commentedon Jun 1, 2026
$500 approved for @mananjadhav