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-11-10] [$175] Home - Empty state text keeps old theme colors after theme change #105469
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
Open Home with at least one empty section showing an empty state.
Go to Account > Preferences > Theme and switch the theme.
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.
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.
⚠️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.
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:
Does it reproduce on staging or production? Those builds may behave differently from dev.
Add a render log in HomeSectionEmptyState and its wrappers. Check whether any of them re-render when Home unfreezes.
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.
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
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.
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:
Expensify Card row:BaseWidgetItem.tsx:63, rendered through AddVirtualCardPersonalDetails.
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:
Read the current values of ThemeContext, ThemeStylesStateContext, ThemeStylesActionsContext and ThemeIllustrationsContext.
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.
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.
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
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]
Activity
MelvinBot commentedon Oct 30, 2026
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.
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.
FreezeWrapperusesreact-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()fromHomeSectionEmptyStateinto 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 instyles/index.ts:4533-4547are correct theme tokens.To confirm, please check:
HomeSectionEmptyStateand its wrappers. Check whether any of them re-render when Home unfreezes.If it is confirmed, a broader fix would re-provide the theme contexts inside
<Freeze>inFreezeWrapper. Then every deep consumer gets the update on unfreeze, not just this component.Investigation details
TabNavigator.tsx:75-81. The nativeFreezeWrapperreturns its children unchanged, so this can only happen on web.ThemeStylesContextProviderbuilds new styles for each theme and passes them through context. There is no module-level cache that could hold stale colors.WidgetContainergets 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.Textdefaults totheme.textthroughuseTheme(). Any other text that relies on that default would go stale the same way.HomeSectionEmptyStatewas 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.Next Steps: Reply with
@MelvinBot <your feedback>to refine this analysis.view run · view recording
melvin-bot commentedon Oct 30, 2026
Current assignee @nkdengineer is eligible for the External assigner, not assigning anyone new.
[-]Home - Empty state text keeps old theme colors after theme change[/-][+][$175] Home - Empty state text keeps old theme colors after theme change[/+]melvin-bot commentedon Oct 30, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022116094758241367593
melvin-bot commentedon Oct 30, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
nkdengineer commentedon Oct 30, 2026
@MelvinBot I can reproduce it consistently on both dev and staging.
MelvinBot commentedon Oct 30, 2026
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.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 inFreezeWrapper, not in each component.Proposal
What is the root cause of that problem?
On web, Home is wrapped in
FreezeWrapper. It usesreact-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 asHomePage. 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:
HomeSectionEmptyState.tsx:41, rendered through<EmptyState />and<ChartEmptyState />.InsightTitleDropdown.tsx:85, rendered throughInsightsSectionandInsightsSectionContent.BaseWidgetItem.tsx:63, rendered throughAddVirtualCardPersonalDetails.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:ThemeContext,ThemeStylesStateContext,ThemeStylesActionsContextandThemeIllustrationsContext.<Freeze>, aroundchildren.FreezeWrapperisn'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)
Investigation details and caveats
npm run typecheckpassed.useLocalize) and screens wrapped inScreenFreezeWrapper. 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
nkdengineer commentedon Oct 30, 2026
@MelvinBot Implement main solution for testing.
4 remaining items
mallenexpensify commentedon Nov 3, 2026
I'm assuming this is a regression and not due payment. Parent issue is below, discussing payment thereUpdating based off
[-][$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[/+]melvin-bot commentedon Nov 3, 2026
@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]
mallenexpensify commentedon Nov 4, 2026
@nkdengineer due $175 here
melvin-bot commentedon Nov 4, 2026
Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.