Skip to content

[Due for payment 2026-11-10] [$175] Home - Empty state text keeps old theme colors after theme change #105469

Open

Description

@melvin-bot

Problem

After the theme changes, the empty state title and description text on Home keep the previous theme's colors. This happens when the system theme changes while you are signed out. It also happens when you change the theme manually. The text stays wrong until the page is refreshed. Both light and dark mode are affected.

Steps to reproduce

  1. Open Home with at least one empty section showing an empty state.
  2. Go to Account > Preferences > Theme and switch the theme.
  3. Go back to Home.

Expected: The empty state text uses the new theme's colors.
Actual: The empty state text keeps the old theme's colors. Other Home text, such as the "Recently added" header, updates correctly.

Root cause (from @nkdengineer)

Home is wrapped in FreezeWrapper and is frozen while you are on Preferences. HomeSectionEmptyState reads its text styles from the theme. However, it is always rendered through wrappers that don't read the theme and always get the same props:

  • RecentlyAddedSection/EmptyState
  • ForYouSection/EmptyState
  • ChartEmptyState

When Home unfreezes, React skips these wrappers, so HomeSectionEmptyState never re-renders with the new styles.

Proposed fix

  • Add required titleStyle and descriptionStyle props to HomeSectionEmptyState.
  • In each wrapper, call useThemeStyles() and pass in styles.forYouEmptyStateTitle and styles.forYouEmptyStateDescription.

Reported in Slack.

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mallenexpensify
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 30, 2026
  2. MelvinBot commented on Oct 30, 2026

    @MelvinBot
    Contributor

    ⚠️ Preliminary analysis (low confidence) — treat as a starting point, not a conclusion.

    I could not reproduce this on dev web at 223b285. The proposed fix in the issue body also looks unlikely to work.

    Repro result: I switched light → dark and dark → light in Preferences, then went back to Home without reloading. Both empty states ("All caught up" and "No recent expenses") used the new theme's colors each time.

    Home after light to dark switch Home after dark to light switch

    Why the proposed fix may not help: If the bug is real, the likely cause is that a context change is lost while Home is frozen. FreezeWrapper uses react-freeze, which freezes the tree with Suspense. After unfreeze, a deep theme consumer updates only if it re-renders for another reason, such as new props.

    The proposed fix moves useThemeStyles() from HomeSectionEmptyState into the three wrappers. Those wrappers are also deep consumers and always get the same props, so they would go stale the same way. The text colors in styles/index.ts:4533-4547 are correct theme tokens.

    To confirm, please check:

    1. Does it reproduce on staging or production? Those builds may behave differently from dev.
    2. Add a render log in HomeSectionEmptyState and its wrappers. Check whether any of them re-render when Home unfreezes.
    3. Change the language while Home is frozen. If the empty-state text stays in the old language, that supports the frozen-context cause.

    If it is confirmed, a broader fix would re-provide the theme contexts inside <Freeze> in FreezeWrapper. Then every deep consumer gets the update on unfreeze, not just this component.

    Investigation details
    • Home is wrapped at TabNavigator.tsx:75-81. The native FreezeWrapper returns its children unchanged, so this can only happen on web.
    • ThemeStylesContextProvider builds new styles for each theme and passes them through context. There is no module-level cache that could hold stale colors.
    • The "Recently added" header updates because WidgetContainer gets new props from a parent that re-renders on focus. The empty-state wrappers are rendered as constant elements, so nothing forces them to re-render.
    • Text defaults to theme.text through useTheme(). Any other text that relies on that default would go stale the same way.
    • HomeSectionEmptyState was added in [Home Page] Add Recently added slot to Home. Adding it did not change this behavior, because a wrapper that calls the hook itself is exposed the same way.
    • The lost-context sequence comes from reading the React 19.2.3 source. Nobody has observed it at runtime yet.

    Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis.


    view run · view recording

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 30, 2026
  4. melvin-bot commented on Oct 30, 2026

    @melvin-bot
    Author

    Current assignee @nkdengineer is eligible for the External assigner, not assigning anyone new.

  5. changed the title [-]Home - Empty state text keeps old theme colors after theme change[/-] [+][$175] Home - Empty state text keeps old theme colors after theme change[/+] on Oct 30, 2026
  6. melvin-bot commented on Oct 30, 2026

    @melvin-bot
    Author
  7. melvin-bot commented on Oct 30, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  8. nkdengineer commented on Oct 30, 2026

    @nkdengineer
    Contributor

    @MelvinBot I can reproduce it consistently on both dev and staging.

    Image Image
  9. MelvinBot commented on Oct 30, 2026

    @MelvinBot
    Contributor

    Thanks nkdengineer. You're right: I now reproduce it on dev web. After switching light → dark, both empty-state titles stay dark on the dark background, and I saw it in two runs. My earlier comment was wrong.

    Home after light to dark switch, empty-state titles stale

    Your screenshots show the bug is wider than HomeSectionEmptyState. The "Spend over time" title and the "Add your details…" line in Time sensitive are stale too. So the fix belongs in FreezeWrapper, not in each component.

    Proposal

    What is the root cause of that problem?

    On web, Home is wrapped in FreezeWrapper. It uses react-freeze, which suspends the tree while you're on Preferences. A theme change made during the freeze reaches only the first theme reader on each branch, such as HomePage. React drops the rest of that work for the suspended tree.

    After unfreeze, the theme provider has nothing new to report, so deeper readers update only if they re-render for another reason. Readers behind a parent that ignores the theme and gets the same props stay stale:

    To-do rows use the same BaseWidgetItem, but they update. Their parent, ForYouSection, reads the theme itself and re-renders on focus.

    What changes do you think we should make in order to solve the problem?

    In FreezeWrapper:

    1. Read the current values of ThemeContext, ThemeStylesStateContext, ThemeStylesActionsContext and ThemeIllustrationsContext.
    2. Provide those same values again inside <Freeze>, around children.

    FreezeWrapper isn't frozen, so it gets the new theme right away. On unfreeze, React sees that the inner providers changed and updates every theme reader below them, at any depth. This fixes all three cases above and any other frozen screen, with no remount.

    You also need to export the two styles contexts from ThemeStylesContextProvider/index.tsx.

    What alternative solutions did you explore? (Optional)

    • Pass styles as props from each wrapper (the issue body's fix): the wrappers are also deep theme readers with unchanged props, so they go stale the same way. It also misses the "Spend over time" and Time sensitive cases.
    • Key the frozen subtree by theme: this always works, but it remounts Home on every theme change and loses scroll position and input state.
    Investigation details and caveats
    • Typecheck: I applied the change locally, and npm run typecheck passed.
    • Runtime: I could not confirm the fix in the browser. After the hot reload the page went blank, and this test session can't reload. I don't know whether hot reload or the change caused the blank page, so please test the fix from a fresh page load.
    • Repro asymmetry: in my run, dark → light looked correct, unlike your screenshot. My account has no Insights or Time sensitive cards, so fewer elements could show the bug.
    • Mechanism source: the explanation comes from reading react-dom 19.2.3: lazy context propagation, which stops at the first reader on a branch, and Suspense dropping the hidden tree's pending work. Nobody has traced it at runtime yet.
    • Same exposure: other contexts that change while Home is frozen can go stale the same way. Examples are language (useLocalize) and screens wrapped in ScreenFreezeWrapper. Those are out of scope here.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/105469) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run · no recording available

  10. nkdengineer commented on Oct 30, 2026

    @nkdengineer
    Contributor

    @MelvinBot Implement main solution for testing.

  11. 4 remaining items

  12. mallenexpensify commented on Nov 3, 2026

    @mallenexpensify
    Contributor

    I'm assuming this is a regression and not due payment. Parent issue is below, discussing payment there

    Updating based off

  13. added and removed
    ReviewingHas a PR in review
    on Nov 3, 2026
  14. changed the title [-][$175] Home - Empty state text keeps old theme colors after theme change[/-] [+][Due for payment 2026-11-10] [$175] Home - Empty state text keeps old theme colors after theme change[/+] on Nov 3, 2026
  15. melvin-bot commented on Nov 3, 2026

    @melvin-bot
    Author

    @nkdengineer

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.30-0 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-11-10. 🎊

    The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]

  16. mallenexpensify commented on Nov 4, 2026

    @mallenexpensify
    Contributor

    @nkdengineer due $175 here

  17. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    on Nov 4, 2026
  18. melvin-bot commented on Nov 4, 2026

    @melvin-bot
    Author

    Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.

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 productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions