Skip to content

[Due for Payment 2026-05-24] label:inbox Extend useDocumentTitle hook to remaining pages across the app #83378

Closed

Description

@yuwenmemon

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:

  1. Open any page outside of Settings (e.g. a report, workspace page, search, etc.)
  2. 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.

Pages already covered (by #81150)

  • src/pages/settings/Profile/ProfilePage.tsx
  • src/pages/settings/Wallet/WalletPage/index.tsx
  • src/pages/settings/Subscription/SubscriptionSettingsPage.tsx
  • src/pages/settings/AboutPage/AboutPage.tsx
  • src/pages/settings/Preferences/PreferencesPage.tsx
  • src/pages/settings/Security/SecuritySettingsPage.tsx
  • src/pages/settings/Troubleshoot/TroubleshootPage.tsx
  • src/pages/settings/Rules/ExpenseRulesPage.tsx
  • src/pages/TeachersUnite/SaveTheWorldPage.tsx

Key areas still needing coverage

  • Workspace pages (src/pages/workspace/)
  • Report pages (src/pages/home/)
  • Search pages (src/pages/Search/)
  • Money request / IOU pages (src/pages/iou/)
  • Travel pages (src/pages/Travel/)
  • Onboarding pages
  • Any other top-level routes

Conventions (established in #81150)

  • Import useDocumentTitle from @hooks/useDocumentTitle
  • Place the hook call after variable declarations in the component
  • No redundant comments on hook usage
  • Use translated strings via useLocalize for the title
Issue OwnerCurrent Issue Owner: @mananjadhav

Activity

  1. self-assigned this
    on Feb 24, 2026
  2. PiyushChandra17 commented on Feb 24, 2026

    @PiyushChandra17
    Contributor

    Hi @yuwenmemon Is this open for external contributors? If so, I'd like to work on this (the details are already specified in OP)

  3. MelvinBot commented on Feb 24, 2026

    @MelvinBot
    Contributor

    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 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:

    1. Create a screen-to-title mapping (e.g., src/libs/Navigation/screenTitles.ts) that maps screen names from SCREENS.ts to translation key paths:

      SCREENS.HOME → 'common.home'
      SCREENS.REPORT → dynamically resolved from report data
      SCREENS.SEARCH.ROOT → 'common.search'
      SCREENS.WORKSPACE.PROFILE → 'workspace.common.overview'
      SCREENS.WORKSPACE.MEMBERS → 'common.members'
      ... etc
      
    2. 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.

    3. 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.

    Pages needing coverage (prioritized):

    • Tier 1 (top-level tabs): HomePage, ReportScreen (Inbox), SearchPage, WorkspacesListPage
    • Tier 2 (workspace central pane — 17 screens): WorkspaceOverviewPage, WorkspaceMembersPage, WorkspaceWorkflowsPage, etc. (all defined in WorkspaceSplitNavigator.tsx:19-38)
    • Tier 3 (domain central pane — 5 screens): DomainInitialPage, DomainSamlPage, DomainAdminsPage, DomainMembersPage, DomainGroupsPage
    • Tier 4 (onboarding — 13 screens): Purpose, PersonalDetails, WorkEmail, etc.
    • Tier 5 (RHP/modal screens): Travel, Money Request, Tasks — lower priority since they're overlays

    What alternative solutions did you explore? (Optional)

    1. 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.

    2. 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.

    3. 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.

  4. yuwenmemon commented on Feb 24, 2026

    @yuwenmemon
    ContributorAuthor

    @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.

  5. MelvinBot commented on Feb 24, 2026

    @MelvinBot
    Contributor

    Per-Page useDocumentTitle Implementation Plan

    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.

    Convention (from PR #81150)

    Every page adds two imports and one hook call:

    import useDocumentTitle from '@hooks/useDocumentTitle';
    import useLocalize from '@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('...')}`)

    File Title Key Rendered
    src/pages/workspace/WorkspaceOverviewPage.tsx workspace.common.profile "Workspace - Overview"
    src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx workspace.common.workflows "Workspace - Workflows"
    src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx workspace.common.invoices "Workspace - Invoices"
    src/pages/workspace/WorkspaceMembersPage.tsx workspace.common.members "Workspace - Members"
    src/pages/workspace/accounting/PolicyAccountingPage.tsx workspace.common.accounting "Workspace - Accounting"
    src/pages/workspace/categories/WorkspaceCategoriesPage.tsx workspace.common.categories "Workspace - Categories"
    src/pages/workspace/WorkspaceMoreFeaturesPage.tsx workspace.common.moreFeatures "Workspace - More features"
    src/pages/workspace/tags/WorkspaceTagsPage.tsx workspace.common.tags "Workspace - Tags"
    src/pages/workspace/taxes/WorkspaceTaxesPage.tsx workspace.common.taxes "Workspace - Taxes"
    src/pages/workspace/reports/WorkspaceReportsPage.tsx common.reports "Workspace - Reports"
    src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx workspace.common.expensifyCard "Workspace - Expensify Card"
    src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx workspace.common.companyCards "Workspace - Company cards"
    src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx common.perDiem "Workspace - Per diem"
    src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx workspace.common.receiptPartners "Workspace - Receipt partners"
    src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx workspace.common.distanceRates "Workspace - Distance rates"
    src/pages/workspace/travel/PolicyTravelPage.tsx workspace.moreFeatures.travel.title "Workspace - Travel"
    src/pages/workspace/rules/PolicyRulesPage.tsx workspace.common.rules "Workspace - Rules"
    src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx workspace.moreFeatures.timeTracking.title "Workspace - Time"

    Note: WorkspaceExpensifyCardPage.tsx is the only file that doesn't already import useLocalize — it will need that import added.

    Batch 3: Domain Central Pane (5 files)

    File Title Key Rendered
    src/pages/domain/DomainInitialPage.tsx (dynamic) Uses domainName variable (e.g., "expensify.com")
    src/pages/domain/DomainSamlPage.tsx domain.saml "SAML"
    src/pages/domain/Admins/DomainAdminsPage.tsx domain.admins.title "Admins"
    src/pages/domain/Members/DomainMembersPage.tsx domain.members.title "Members"
    src/pages/domain/Groups/DomainGroupsPage.tsx domain.groups.title "Groups"

    Domain pages don't have a natural parent label like "Settings" or "Workspace". Options:

    • Option A: Just the page name (e.g., "SAML", "Admins")
    • Option B: Prefix with "Domain" (e.g., "Domain - SAML") — but there's no translate('common.domain') key currently

    Batch 4: Onboarding Modal (13 files)

    These are shown in a modal during first-time setup. Each title uses the page heading text.

    File (Base*.tsx) Title Key Rendered
    src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx onboarding.purpose.title "What do you want to do today?"
    src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx onboarding.whatsYourName "What's your name?"
    src/pages/OnboardingWorkEmail/BaseOnboardingWorkEmail.tsx onboarding.workEmail.title "What's your work email?"
    src/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsx onboarding.workEmailValidation.title "Verify your work email"
    src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx onboarding.peopleYouMayKnow "People you may know..."
    src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx onboarding.joinAWorkspace "Join a workspace"
    src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx onboarding.employees.title "How many employees...?"
    src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx onboarding.accounting.title "Do you use accounting software?"
    src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx onboarding.interestedFeatures.title "What features...?"
    src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx onboarding.workspace.title "Stay organized with a workspace"
    src/pages/OnboardingWorkspaceConfirmation/BaseOnboardingWorkspaceConfirmation.tsx onboarding.confirmWorkspace.title "Confirm workspace"
    src/pages/OnboardingWorkspaceCurrency/BaseOnboardingWorkspaceCurrency.tsx common.currency "Currency"
    src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx onboarding.inviteMembers.title "Invite members"

    Open Questions for the Team

    1. 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.
    2. Domain page prefix — Should domain pages use "Domain - Admins" format (requires adding a translation key) or just "Admins"?
    3. Onboarding priority — These are modal screens seen only once. Should they be included in the initial pass or deferred?
    4. 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.

  6. yuwenmemon commented on Feb 24, 2026

    @yuwenmemon
    ContributorAuthor

    @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")

  7. added
    NewFeatureSomething to build that is a new item.
    and removed
    ImprovementItem broken or needs improvement.
    on Feb 24, 2026
  8. melvin-bot commented on Feb 24, 2026

    @melvin-bot

    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.

  9. 27 remaining items

  10. mallenexpensify commented on May 18, 2026

    @mallenexpensify
    Contributor

    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.

  11. melvin-bot commented on May 18, 2026

    @melvin-bot

    📣 @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:

    1. Make sure you've read and understood the contributing guidelines.
    2. 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.
    3. 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.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  12. mananjadhav commented on May 19, 2026

    @mananjadhav
    Collaborator

    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

    • Have an account with one workspace and a few expenses.

    Test:

    1. Navigate to each of the application pages (Home, Search, Workspaces, any workspace settings page, any domain page, and a report/chat)
    2. Verify the browser tab title updates to reflect the current page context. For instance, visiting Accounts > Profile it should show Profile
    3. Verify that when navigating away, the title updates to the new page.
    4. Verify that workspace pages show "PolicyName - PageName" format
    5. Verify that domain pages show "domain.com - PageName" format
    6. Open the expense report, ensure the title reflects the name of the report.
    7. The title should also reflect the inbox unread count.

    Do we agree 👍 or 👎

    Zapier Logs Run ID: 00040eee-c449-ae0e-12fe-82ba3e9ecee4
  13. mananjadhav commented on May 19, 2026

    @mananjadhav
    Collaborator

    @mallenexpensify The checklist completed.

  14. mananjadhav commented on May 22, 2026

    @mananjadhav
    Collaborator

    @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.

  15. mallenexpensify commented on May 25, 2026

    @mallenexpensify
    Contributor

    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.

    Shows deployed to production on 5/17. Also, can you address this @mananjadhav

    Deploy Blocker #85352 was identified to be related to this PR.

  16. mananjadhav commented on May 26, 2026

    @mananjadhav
    Collaborator

    @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.

  17. mallenexpensify commented on May 26, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @mananjadhav due $500 via NewDot

  18. mananjadhav commented on May 29, 2026

    @mananjadhav
    Collaborator

    Seems like @joekaufmanexpensify got a C+ status ;)

    Contributor+: @joekaufmanexpensify due $500 via NewDot

    @mallenexpensify can you correct the payment summary here?

  19. mallenexpensify commented on May 29, 2026

    @mallenexpensify
    Contributor

    @mananjadhav updated. GH sux sometimes

    Image
  20. garrettmknight commented on Jun 1, 2026

    @garrettmknight
    Contributor

    $500 approved for @mananjadhav

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2Melvin TriageAutomatic AI Analysis of an issueNewFeatureSomething to build that is a new item.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions