From 414997487cf96aa3f0b8f9264cf180b173ef6ea3 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 07:47:50 -0700
Subject: [PATCH 01/35] Extract chartWebFont helper for chart font loading

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/chartWebFont.ts | 11 ++++++++
 .../useChartFontManager.ts                    | 27 +++++++------------
 2 files changed, 21 insertions(+), 17 deletions(-)
 create mode 100644 src/components/Charts/context/chartWebFont.ts

diff --git a/src/components/Charts/context/chartWebFont.ts b/src/components/Charts/context/chartWebFont.ts
new file mode 100644
index 000000000000..a569e1424586
--- /dev/null
+++ b/src/components/Charts/context/chartWebFont.ts
@@ -0,0 +1,11 @@
+import type {DataModule} from '@shopify/react-native-skia';
+
+function chartWebFont(url: string): DataModule {
+    // We construct a fake ESModule-shaped object because react-native-skia's `useFonts` on web expects
+    // a DataModule (i.e. the result of a dynamic `require()` call), which always has the shape
+    // `{ __esModule: true, default: <url> }`. The `__esModule` property uses a double-underscore prefix
+    // that violates the naming-convention rule, but it is mandated by the library's internal contract.
+    return {__esModule: true, default: url} as unknown as DataModule;
+}
+
+export default chartWebFont;
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
index e0c8a8305fed..f3dbf01b77f2 100644
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
+++ b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
@@ -1,30 +1,23 @@
-import type {DataModule, SkTypefaceFontProvider} from '@shopify/react-native-skia';
+import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
 import {useFonts, useTypeface} from '@shopify/react-native-skia';
-
-function webFont(url: string): DataModule {
-    // We construct a fake ESModule-shaped object because react-native-skia's `useFonts` on web expects
-    // a DataModule (i.e. the result of a dynamic `require()` call), which always has the shape
-    // `{ __esModule: true, default: <url> }`. The `__esModule` property uses a double-underscore prefix
-    // that violates the naming-convention rule, but it is mandated by the library's internal contract.
-    return {__esModule: true, default: url} as unknown as DataModule;
-}
+import chartWebFont from '@components/Charts/context/chartWebFont';
 
 function useChartFontManager(): SkTypefaceFontProvider | null {
     return useFonts({
         ExpensifyNeue: [
-            webFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string),
-            webFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string),
-            webFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string),
-            webFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string),
+            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string),
+            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string),
+            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string),
+            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string),
         ],
-        NotoSansSymbols: [webFont(require('@assets/fonts/NotoSans-Symbols.ttf') as string)],
-        NotoSansSCMonths: [webFont(require('@assets/fonts/NotoSansSC-Months.ttf') as string)],
+        NotoSansSymbols: [chartWebFont(require('@assets/fonts/NotoSans-Symbols.ttf') as string)],
+        NotoSansSCMonths: [chartWebFont(require('@assets/fonts/NotoSansSC-Months.ttf') as string)],
     });
 }
 
 function useChartDefaultTypeface() {
-    const regular = useTypeface(webFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
-    const bold = useTypeface(webFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
+    const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
+    const bold = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
     return {regular, bold};
 }
 

From b82f7395f2350b04a576abdde0f6aa4e012400b8 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 07:47:58 -0700
Subject: [PATCH 02/35] Add ChartDefaultTypefaceContext and platform providers

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../context/ChartDefaultTypefaceContext.tsx   | 20 +++++++++++++++++++
 .../ChartDefaultTypefaceProvider.native.tsx   | 15 ++++++++++++++
 .../context/ChartDefaultTypefaceProvider.tsx  | 15 ++++++++++++++
 3 files changed, 50 insertions(+)
 create mode 100644 src/components/Charts/context/ChartDefaultTypefaceContext.tsx
 create mode 100644 src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
 create mode 100644 src/components/Charts/context/ChartDefaultTypefaceProvider.tsx

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
new file mode 100644
index 000000000000..eba804949470
--- /dev/null
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -0,0 +1,20 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+import React, {createContext, useContext} from 'react';
+
+type ChartDefaultTypeface = {
+    regular: SkTypeface | null;
+    bold: SkTypeface | null;
+};
+
+const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
+
+function useChartDefaultTypeface(): ChartDefaultTypeface {
+    const context = useContext(ChartDefaultTypefaceContext);
+    if (!context) {
+        throw new Error('useChartDefaultTypeface must be used within ChartDefaultTypefaceProvider');
+    }
+    return context;
+}
+
+export {ChartDefaultTypefaceContext, useChartDefaultTypeface};
+export type {ChartDefaultTypeface};
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
new file mode 100644
index 000000000000..1d3080eeef85
--- /dev/null
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -0,0 +1,15 @@
+import type {DataModule} from '@shopify/react-native-skia';
+import {useTypeface} from '@shopify/react-native-skia';
+import React from 'react';
+import {ChartDefaultTypefaceContext} from '@components/Charts/context/ChartDefaultTypefaceContext';
+
+function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
+    const regular = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule);
+    const bold = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule);
+
+    return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
+}
+
+ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
+
+export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
new file mode 100644
index 000000000000..574dce4d3ab7
--- /dev/null
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -0,0 +1,15 @@
+import {useTypeface} from '@shopify/react-native-skia';
+import React from 'react';
+import {ChartDefaultTypefaceContext} from '@components/Charts/context/ChartDefaultTypefaceContext';
+import chartWebFont from '@components/Charts/context/chartWebFont';
+
+function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
+    const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
+    const bold = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
+
+    return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
+}
+
+ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
+
+export default ChartDefaultTypefaceProvider;

From a3b46874dc0685a218d3e6300ef0eeac647f5ba5 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 07:48:10 -0700
Subject: [PATCH 03/35] Wire Victory charts through
 ChartDefaultTypefaceProvider

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/hooks/index.ts                |  5 ++++-
 .../useChartFontManager.native.ts                   |  9 +--------
 .../useChartFontManager/useChartFontManager.ts      |  9 +--------
 .../BaseVictoryChartRenderer.tsx                    | 13 ++++++++-----
 4 files changed, 14 insertions(+), 22 deletions(-)

diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index c18d0c9934d4..0b2644373cd1 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -2,7 +2,10 @@ export {default as useChartLabelLayout} from './useChartLabelLayout';
 export {default as useChartLabelMeasurements} from './useChartLabelMeasurements';
 export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
-export {default as useChartFontManager, useChartDefaultTypeface} from './useChartFontManager/useChartFontManager';
+export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
+export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
+export {ChartDefaultTypefaceContext, useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
+export type {ChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
index 04cd0344d420..5b3f293c2def 100644
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
+++ b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
@@ -1,5 +1,5 @@
 import type {DataModule, SkTypefaceFontProvider} from '@shopify/react-native-skia';
-import {useFonts, useTypeface} from '@shopify/react-native-skia';
+import {useFonts} from '@shopify/react-native-skia';
 
 function useChartFontManager(): SkTypefaceFontProvider | null {
     return useFonts({
@@ -14,11 +14,4 @@ function useChartFontManager(): SkTypefaceFontProvider | null {
     });
 }
 
-function useChartDefaultTypeface() {
-    const regular = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule);
-    const bold = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule);
-    return {regular, bold};
-}
-
-export {useChartDefaultTypeface};
 export default useChartFontManager;
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
index f3dbf01b77f2..f43896f3fb14 100644
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
+++ b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
@@ -1,5 +1,5 @@
 import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
-import {useFonts, useTypeface} from '@shopify/react-native-skia';
+import {useFonts} from '@shopify/react-native-skia';
 import chartWebFont from '@components/Charts/context/chartWebFont';
 
 function useChartFontManager(): SkTypefaceFontProvider | null {
@@ -15,11 +15,4 @@ function useChartFontManager(): SkTypefaceFontProvider | null {
     });
 }
 
-function useChartDefaultTypeface() {
-    const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
-    const bold = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
-    return {regular, bold};
-}
-
-export {useChartDefaultTypeface};
 export default useChartFontManager;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
index da522351285b..a84bb3b17166 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
@@ -1,4 +1,5 @@
 import React from 'react';
+import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
 import VictoryChartContainer from './components/VictoryChartContainer';
 import VictoryChartContent from './components/VictoryChartContent';
 import {VictoryChartProvider} from './context/VictoryChartContext';
@@ -6,11 +7,13 @@ import type {VictoryChartRendererProps} from './types';
 
 function BaseVictoryChartRenderer({tnode}: VictoryChartRendererProps) {
     return (
-        <VictoryChartProvider tnode={tnode}>
-            <VictoryChartContainer>
-                <VictoryChartContent />
-            </VictoryChartContainer>
-        </VictoryChartProvider>
+        <ChartDefaultTypefaceProvider>
+            <VictoryChartProvider tnode={tnode}>
+                <VictoryChartContainer>
+                    <VictoryChartContent />
+                </VictoryChartContainer>
+            </VictoryChartProvider>
+        </ChartDefaultTypefaceProvider>
     );
 }
 

From 5af76f468145e665613b4445fa06bd858611479b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 07:49:52 -0700
Subject: [PATCH 04/35] Fix lint in chart typeface context files

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/ChartDefaultTypefaceContext.tsx | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.native.tsx    | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.tsx           | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index eba804949470..c358c99a667c 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -1,5 +1,5 @@
 import type {SkTypeface} from '@shopify/react-native-skia';
-import React, {createContext, useContext} from 'react';
+import {createContext, useContext} from 'react';
 
 type ChartDefaultTypeface = {
     regular: SkTypeface | null;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index 1d3080eeef85..99ab33957b55 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -1,7 +1,7 @@
 import type {DataModule} from '@shopify/react-native-skia';
 import {useTypeface} from '@shopify/react-native-skia';
 import React from 'react';
-import {ChartDefaultTypefaceContext} from '@components/Charts/context/ChartDefaultTypefaceContext';
+import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const regular = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule);
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index 574dce4d3ab7..ca490da0a19d 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -1,7 +1,7 @@
 import {useTypeface} from '@shopify/react-native-skia';
 import React from 'react';
-import {ChartDefaultTypefaceContext} from '@components/Charts/context/ChartDefaultTypefaceContext';
-import chartWebFont from '@components/Charts/context/chartWebFont';
+import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
+import chartWebFont from './chartWebFont';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));

From a13a7a5bf38d204b65ff921d9c655cdf0cb5b9bb Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 08:09:52 -0700
Subject: [PATCH 05/35] Remove unused chart typeface exports for knip

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/ChartDefaultTypefaceContext.tsx | 1 -
 src/components/Charts/hooks/index.ts                          | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index c358c99a667c..0c8223d73640 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -17,4 +17,3 @@ function useChartDefaultTypeface(): ChartDefaultTypeface {
 }
 
 export {ChartDefaultTypefaceContext, useChartDefaultTypeface};
-export type {ChartDefaultTypeface};
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 0b2644373cd1..64d0ed2ae308 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,8 +4,7 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
 export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
-export {ChartDefaultTypefaceContext, useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
-export type {ChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
+export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';

From 90961765be326669dbe3f3a72fbeec0b450a5cf0 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 08:41:22 -0700
Subject: [PATCH 06/35] Remove unnecessary displayName

---
 .../Charts/context/ChartDefaultTypefaceProvider.native.tsx      | 2 --
 src/components/Charts/context/ChartDefaultTypefaceProvider.tsx  | 2 --
 2 files changed, 4 deletions(-)

diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index 99ab33957b55..f9830eba9434 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -10,6 +10,4 @@ function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
-ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
-
 export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index ca490da0a19d..7dd6886d3300 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -10,6 +10,4 @@ function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
-ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
-
 export default ChartDefaultTypefaceProvider;

From 4318681f8159e88cb7e22e599ae4409a550dceae Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 08:50:28 -0700
Subject: [PATCH 07/35] Wrap Skia chart overlays with
 ChartDefaultTypefaceProvider

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../components/VictoryChartCartesian.tsx      | 31 ++++++++-------
 .../components/VictoryChartPolar.tsx          | 39 ++++++++++---------
 2 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 10cef4f0875c..2beed92cd08f 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
+import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -25,20 +26,22 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <VictoryChartRenderArgsProvider value={renderArgs}>
-                    {labelItems.map((labelItem) => (
-                        <VictoryChartLabel
-                            key={`label-${labelItem.x}-${labelItem.y}`}
-                            {...labelItem}
-                        />
-                    ))}
-                    {legendItems.map((legendItem) => (
-                        <VictoryChartLegend
-                            key={`legend-${legendItem.x}-${legendItem.y}`}
-                            {...legendItem}
-                        />
-                    ))}
-                </VictoryChartRenderArgsProvider>
+                <ChartDefaultTypefaceProvider>
+                    <VictoryChartRenderArgsProvider value={renderArgs}>
+                        {labelItems.map((labelItem) => (
+                            <VictoryChartLabel
+                                key={`label-${labelItem.x}-${labelItem.y}`}
+                                {...labelItem}
+                            />
+                        ))}
+                        {legendItems.map((legendItem) => (
+                            <VictoryChartLegend
+                                key={`legend-${legendItem.x}-${legendItem.y}`}
+                                {...legendItem}
+                            />
+                        ))}
+                    </VictoryChartRenderArgsProvider>
+                </ChartDefaultTypefaceProvider>
             )}
         >
             {(renderArgs) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
index ebaa6856e2f9..d9c921e437bf 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import {PolarChart} from 'victory-native';
+import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
 import {COLOR_KEY, LABEL_KEY, VALUE_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -20,24 +21,26 @@ function VictoryChartPolar() {
             valueKey={VALUE_KEY}
             colorKey={COLOR_KEY}
         >
-            {tnode.children.map((child) => (
-                <VictoryChartCategories
-                    key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
-                    tnode={child}
-                />
-            ))}
-            {labelItems.map((labelItem) => (
-                <VictoryChartLabel
-                    key={`label-${labelItem.x}-${labelItem.y}`}
-                    {...labelItem}
-                />
-            ))}
-            {legendItems.map((legendItem) => (
-                <VictoryChartLegend
-                    key={`legend-${legendItem.x}-${legendItem.y}`}
-                    {...legendItem}
-                />
-            ))}
+            <ChartDefaultTypefaceProvider>
+                {tnode.children.map((child) => (
+                    <VictoryChartCategories
+                        key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
+                        tnode={child}
+                    />
+                ))}
+                {labelItems.map((labelItem) => (
+                    <VictoryChartLabel
+                        key={`label-${labelItem.x}-${labelItem.y}`}
+                        {...labelItem}
+                    />
+                ))}
+                {legendItems.map((legendItem) => (
+                    <VictoryChartLegend
+                        key={`legend-${legendItem.x}-${legendItem.y}`}
+                        {...legendItem}
+                    />
+                ))}
+            </ChartDefaultTypefaceProvider>
         </PolarChart>
     );
 }

From 1e5309f7db60664666ab682a8acb27c44357f739 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:08:24 -0700
Subject: [PATCH 08/35] Load Expensify New Kansas for Victory chart labels

Polar chart HTML specifies fontFamily per label line; parse it and
select the Kansas typeface from ChartDefaultTypefaceProvider.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../context/ChartDefaultTypefaceContext.tsx       |  1 +
 .../ChartDefaultTypefaceProvider.native.tsx       |  3 ++-
 .../context/ChartDefaultTypefaceProvider.tsx      |  3 ++-
 src/components/Charts/hooks/index.ts              |  1 +
 .../components/VictoryChartLabel.tsx              |  8 +++++---
 .../parsers/victoryLabelParser.ts                 |  7 +++++++
 .../HTMLRenderers/VictoryChartRenderer/types.ts   |  4 ++++
 .../utils/getVictoryChartLabelTypeface.ts         | 15 +++++++++++++++
 8 files changed, 37 insertions(+), 5 deletions(-)
 create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index 0c8223d73640..37ff2010aead 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -4,6 +4,7 @@ import {createContext, useContext} from 'react';
 type ChartDefaultTypeface = {
     regular: SkTypeface | null;
     bold: SkTypeface | null;
+    kansas: SkTypeface | null;
 };
 
 const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index f9830eba9434..332a0d3577a4 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -6,8 +6,9 @@ import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const regular = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule);
     const bold = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule);
+    const kansas = useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-Medium.otf') as DataModule);
 
-    return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
+    return <ChartDefaultTypefaceContext.Provider value={{regular, bold, kansas}}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
 export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index 7dd6886d3300..e541039477b2 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -6,8 +6,9 @@ import chartWebFont from './chartWebFont';
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
     const bold = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
+    const kansas = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-Medium.woff2') as string));
 
-    return <ChartDefaultTypefaceContext.Provider value={{regular, bold}}>{children}</ChartDefaultTypefaceContext.Provider>;
+    return <ChartDefaultTypefaceContext.Provider value={{regular, bold, kansas}}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
 export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 64d0ed2ae308..988df7608392 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -5,6 +5,7 @@ export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
 export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
 export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
+export type {ChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
index 76e3f963bda1..ee634e570847 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
@@ -4,6 +4,7 @@ import React from 'react';
 import {useChartDefaultTypeface} from '@components/Charts/hooks';
 import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import computeTextAnchorPosition from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/computeTextAnchorPosition';
+import getVictoryChartLabelTypeface from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface';
 
 type VictoryChartLabelsProps = LabelItem;
 
@@ -20,15 +21,16 @@ type ProcessedLine = {
  * Renders floating Skia text labels (from `<victorylabel>` nodes) over the chart canvas.
  * Intended for use inside CartesianChart's `renderOutside` callback.
  */
-function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, lineHeight, textAnchor = 'start', verticalAnchor = 'start'}: VictoryChartLabelsProps) {
-    const {regular: regularTypeface, bold: boldTypeface} = useChartDefaultTypeface();
+function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily, lineHeight, textAnchor = 'start', verticalAnchor = 'start'}: VictoryChartLabelsProps) {
+    const typefaces = useChartDefaultTypeface();
     const processedLines = text.split('\n').reduce(
         (acc, line, index) => {
             const lineColor = color?.[index];
             const lineFontSize = fontSize?.[index];
             const lineFontWeight = fontWeight?.[index];
+            const lineFontFamily = fontFamily?.[index];
             const lineLineHeight = lineHeight?.[index];
-            const typeface = lineFontWeight === 'bold' ? boldTypeface : regularTypeface;
+            const typeface = getVictoryChartLabelTypeface(lineFontFamily, lineFontWeight, typefaces);
             const lineFont = typeface && lineFontSize ? Skia.Font(typeface, lineFontSize) : null;
             const fontMetrics = lineFont?.getMetrics();
             const lineWidth = lineFont?.getGlyphWidths(lineFont.getGlyphIDs(line)).reduce((totalWidth, width) => totalWidth + width, 0) ?? 0;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
index a244f0b8c32e..1cb115c2d28d 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
@@ -13,6 +13,7 @@ function parseVictoryLabelNode(tnode: TNode): PartialProcessNodeResult {
         color: {},
         fontSize: {},
         fontWeight: {},
+        fontFamily: {},
         lineHeight: parseAttribute<number[]>(tnode.attributes.lineheight),
         textAnchor: parseAttribute<TextAnchor>(tnode.attributes.textanchor),
         verticalAnchor: parseAttribute<TextAnchor>(tnode.attributes.verticalanchor),
@@ -40,6 +41,12 @@ function parseVictoryLabelNode(tnode: TNode): PartialProcessNodeResult {
                     [index]: Number(textStyle.fontWeight) === 700 ? 'bold' : 'normal',
                 };
             }
+            if (textStyle.fontFamily) {
+                labelItem.fontFamily = {
+                    ...labelItem.fontFamily,
+                    [index]: textStyle.fontFamily,
+                };
+            }
         }
     }
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
index 51f52d6656c3..0b8007abfd83 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
@@ -36,6 +36,7 @@ type RawLabelStyle = {
     fill?: Color;
     fontSize?: string | number;
     fontWeight?: string | number;
+    fontFamily?: string;
 };
 
 type RawLegendStyle = {
@@ -81,6 +82,9 @@ type LabelItem = {
     /** Font weight (per line) */
     fontWeight?: Record<number, 'normal' | 'bold'>;
 
+    /** Font family (per line) */
+    fontFamily?: Record<number, string>;
+
     /** Line height (per line) */
     lineHeight?: Record<number, number>;
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts
new file mode 100644
index 000000000000..62081790f762
--- /dev/null
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts
@@ -0,0 +1,15 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+import type {ChartDefaultTypeface} from '@components/Charts/hooks';
+
+/** Matches `fontFamily` in Victory HTML from Web-Expensify (e.g. proactive CFO polar charts). */
+const EXPENSIFY_NEW_KANSAS_FONT_FAMILY = 'Expensify New Kansas';
+
+function getVictoryChartLabelTypeface(fontFamily: string | undefined, fontWeight: 'normal' | 'bold' | undefined, typefaces: ChartDefaultTypeface): SkTypeface | null {
+    if (fontFamily === EXPENSIFY_NEW_KANSAS_FONT_FAMILY) {
+        return typefaces.kansas;
+    }
+
+    return fontWeight === 'bold' ? typefaces.bold : typefaces.regular;
+}
+
+export default getVictoryChartLabelTypeface;

From b78dfa1aa632f5d18bc7b23e8c998a771a27b551 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:13:50 -0700
Subject: [PATCH 09/35] Load all app Skia typefaces in
 ChartDefaultTypefaceProvider

Expose every FontUtils family variant (Neue, Mono, Kansas, emoji)
and resolve Victory label styles via getChartSkiaTypeface.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../context/ChartDefaultTypefaceContext.tsx   |  8 +--
 .../ChartDefaultTypefaceProvider.native.tsx   | 11 ++--
 .../context/ChartDefaultTypefaceProvider.tsx  | 11 ++--
 .../Charts/context/chartSkiaTypefaceTypes.ts  |  9 +++
 .../Charts/context/getChartSkiaTypeface.ts    | 59 +++++++++++++++++++
 .../context/useChartSkiaTypefaces.native.ts   | 21 +++++++
 .../Charts/context/useChartSkiaTypefaces.ts   | 21 +++++++
 src/components/Charts/hooks/index.ts          |  3 +-
 .../components/VictoryChartLabel.tsx          | 12 ++--
 .../components/VictoryChartLegend.tsx         |  8 +--
 .../context/VictoryChartContext.tsx           |  4 +-
 .../parsers/victoryLabelParser.ts             |  7 +++
 .../parsers/victoryLegendParser.ts            |  4 +-
 .../VictoryChartRenderer/types.ts             | 12 ++++
 .../utils/getVictoryChartLabelTypeface.ts     | 15 -----
 15 files changed, 159 insertions(+), 46 deletions(-)
 create mode 100644 src/components/Charts/context/chartSkiaTypefaceTypes.ts
 create mode 100644 src/components/Charts/context/getChartSkiaTypeface.ts
 create mode 100644 src/components/Charts/context/useChartSkiaTypefaces.native.ts
 create mode 100644 src/components/Charts/context/useChartSkiaTypefaces.ts
 delete mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index 37ff2010aead..6a0e783d8354 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -1,11 +1,5 @@
-import type {SkTypeface} from '@shopify/react-native-skia';
 import {createContext, useContext} from 'react';
-
-type ChartDefaultTypeface = {
-    regular: SkTypeface | null;
-    bold: SkTypeface | null;
-    kansas: SkTypeface | null;
-};
+import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
 
 const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
 
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index 332a0d3577a4..0a7384e5a622 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -1,14 +1,13 @@
-import type {DataModule} from '@shopify/react-native-skia';
-import {useTypeface} from '@shopify/react-native-skia';
 import React from 'react';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
+import useChartSkiaTypefaces from './useChartSkiaTypefaces';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
-    const regular = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule);
-    const bold = useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule);
-    const kansas = useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-Medium.otf') as DataModule);
+    const typefaces = useChartSkiaTypefaces();
 
-    return <ChartDefaultTypefaceContext.Provider value={{regular, bold, kansas}}>{children}</ChartDefaultTypefaceContext.Provider>;
+    return <ChartDefaultTypefaceContext.Provider value={typefaces}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
+ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
+
 export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index e541039477b2..0a7384e5a622 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -1,14 +1,13 @@
-import {useTypeface} from '@shopify/react-native-skia';
 import React from 'react';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
-import chartWebFont from './chartWebFont';
+import useChartSkiaTypefaces from './useChartSkiaTypefaces';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
-    const regular = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string));
-    const bold = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string));
-    const kansas = useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-Medium.woff2') as string));
+    const typefaces = useChartSkiaTypefaces();
 
-    return <ChartDefaultTypefaceContext.Provider value={{regular, bold, kansas}}>{children}</ChartDefaultTypefaceContext.Provider>;
+    return <ChartDefaultTypefaceContext.Provider value={typefaces}>{children}</ChartDefaultTypefaceContext.Provider>;
 }
 
+ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
+
 export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/chartSkiaTypefaceTypes.ts b/src/components/Charts/context/chartSkiaTypefaceTypes.ts
new file mode 100644
index 000000000000..fa26dde27a2a
--- /dev/null
+++ b/src/components/Charts/context/chartSkiaTypefaceTypes.ts
@@ -0,0 +1,9 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+// eslint-disable-next-line no-restricted-imports
+import type FontFamilyStyles from '@styles/utils/FontUtils/fontFamily/types';
+
+type ChartSkiaTypefaceKey = Exclude<keyof FontFamilyStyles, 'SYSTEM'>;
+
+type ChartDefaultTypeface = Record<ChartSkiaTypefaceKey, SkTypeface | null>;
+
+export type {ChartDefaultTypeface, ChartSkiaTypefaceKey};
diff --git a/src/components/Charts/context/getChartSkiaTypeface.ts b/src/components/Charts/context/getChartSkiaTypeface.ts
new file mode 100644
index 000000000000..eecf10edfa9e
--- /dev/null
+++ b/src/components/Charts/context/getChartSkiaTypeface.ts
@@ -0,0 +1,59 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+import type {TextStyle} from 'react-native';
+// eslint-disable-next-line no-restricted-imports
+import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
+import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from './chartSkiaTypefaceTypes';
+
+type ChartLabelFontStyle = 'normal' | 'italic';
+type ChartLabelFontWeight = 'normal' | 'bold';
+
+function normalizeFontStyle(fontStyle: string | undefined): ChartLabelFontStyle {
+    return fontStyle === 'italic' ? 'italic' : 'normal';
+}
+
+function normalizeFontWeight(fontWeight: string | number | undefined): ChartLabelFontWeight {
+    return Number(fontWeight) === 700 ? 'bold' : 'normal';
+}
+
+function fontWeightMatches(definitionWeight: TextStyle['fontWeight'], labelWeight: ChartLabelFontWeight): boolean {
+    const numericWeight = Number(definitionWeight);
+
+    if (labelWeight === 'bold') {
+        return numericWeight >= 600 || definitionWeight === 'bold';
+    }
+
+    return numericWeight < 600 && definitionWeight !== 'bold';
+}
+
+function getChartSkiaTypefaceKey(fontFamily: string | undefined, fontStyle: ChartLabelFontStyle, fontWeight: ChartLabelFontWeight): ChartSkiaTypefaceKey {
+    const resolvedFontFamily = fontFamily ?? singleFontFamily.EXP_NEUE.fontFamily;
+
+    if (resolvedFontFamily === singleFontFamily.EXP_NEW_KANSAS_MEDIUM.fontFamily) {
+        return fontStyle === 'italic' ? 'EXP_NEW_KANSAS_MEDIUM_ITALIC' : 'EXP_NEW_KANSAS_MEDIUM';
+    }
+
+    const matchingEntry = (Object.entries(singleFontFamily) as Array<[ChartSkiaTypefaceKey, (typeof singleFontFamily)[ChartSkiaTypefaceKey]]>).find(
+        ([, definition]) => definition.fontFamily === resolvedFontFamily && normalizeFontStyle(definition.fontStyle) === fontStyle && fontWeightMatches(definition.fontWeight, fontWeight),
+    );
+
+    return matchingEntry?.[0] ?? 'EXP_NEUE';
+}
+
+function getChartSkiaTypeface(
+    typefaces: ChartDefaultTypeface,
+    {
+        fontFamily,
+        fontStyle,
+        fontWeight,
+    }: {
+        fontFamily?: string;
+        fontStyle?: string;
+        fontWeight?: string | number;
+    },
+): SkTypeface | null {
+    const typefaceKey = getChartSkiaTypefaceKey(fontFamily, normalizeFontStyle(fontStyle), normalizeFontWeight(fontWeight));
+    return typefaces[typefaceKey];
+}
+
+export {getChartSkiaTypeface};
+export type {ChartLabelFontStyle, ChartLabelFontWeight};
diff --git a/src/components/Charts/context/useChartSkiaTypefaces.native.ts b/src/components/Charts/context/useChartSkiaTypefaces.native.ts
new file mode 100644
index 000000000000..9f47af1236fd
--- /dev/null
+++ b/src/components/Charts/context/useChartSkiaTypefaces.native.ts
@@ -0,0 +1,21 @@
+import type {DataModule} from '@shopify/react-native-skia';
+import {useTypeface} from '@shopify/react-native-skia';
+import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
+
+function useChartSkiaTypefaces(): ChartDefaultTypeface {
+    return {
+        MONOSPACE: useTypeface(require('@assets/fonts/native/ExpensifyMono-Regular.otf') as DataModule),
+        MONOSPACE_BOLD: useTypeface(require('@assets/fonts/native/ExpensifyMono-Bold.otf') as DataModule),
+        MONOSPACE_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyMono-Italic.otf') as DataModule),
+        MONOSPACE_BOLD_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyMono-BoldItalic.otf') as DataModule),
+        EXP_NEUE: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule),
+        EXP_NEUE_BOLD: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule),
+        EXP_NEUE_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Italic.otf') as DataModule),
+        EXP_NEUE_BOLD_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNeue-BoldItalic.otf') as DataModule),
+        EXP_NEW_KANSAS_MEDIUM: useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-Medium.otf') as DataModule),
+        EXP_NEW_KANSAS_MEDIUM_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf') as DataModule),
+        CUSTOM_EMOJI_FONT: useTypeface(require('@assets/fonts/native/CustomEmojiNativeFont.ttf') as DataModule),
+    };
+}
+
+export default useChartSkiaTypefaces;
diff --git a/src/components/Charts/context/useChartSkiaTypefaces.ts b/src/components/Charts/context/useChartSkiaTypefaces.ts
new file mode 100644
index 000000000000..1125675aa155
--- /dev/null
+++ b/src/components/Charts/context/useChartSkiaTypefaces.ts
@@ -0,0 +1,21 @@
+import {useTypeface} from '@shopify/react-native-skia';
+import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
+import chartWebFont from './chartWebFont';
+
+function useChartSkiaTypefaces(): ChartDefaultTypeface {
+    return {
+        MONOSPACE: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Regular.woff2') as string)),
+        MONOSPACE_BOLD: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Bold.woff2') as string)),
+        MONOSPACE_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Italic.woff2') as string)),
+        MONOSPACE_BOLD_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-BoldItalic.woff2') as string)),
+        EXP_NEUE: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string)),
+        EXP_NEUE_BOLD: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string)),
+        EXP_NEUE_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string)),
+        EXP_NEUE_BOLD_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string)),
+        EXP_NEW_KANSAS_MEDIUM: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-Medium.woff2') as string)),
+        EXP_NEW_KANSAS_MEDIUM_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-MediumItalic.woff2') as string)),
+        CUSTOM_EMOJI_FONT: useTypeface(chartWebFont(require('@assets/fonts/web/CustomEmojiWebFont.ttf') as string)),
+    };
+}
+
+export default useChartSkiaTypefaces;
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 988df7608392..f84d2716cbb8 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,8 +4,9 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
 export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
+export {getChartSkiaTypeface} from '../context/getChartSkiaTypeface';
 export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
-export type {ChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
+export type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '../context/chartSkiaTypefaceTypes';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
index ee634e570847..d59e52873a6c 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
@@ -1,10 +1,9 @@
 import {Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React from 'react';
-import {useChartDefaultTypeface} from '@components/Charts/hooks';
+import {getChartSkiaTypeface, useChartDefaultTypeface} from '@components/Charts/hooks';
 import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import computeTextAnchorPosition from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/computeTextAnchorPosition';
-import getVictoryChartLabelTypeface from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface';
 
 type VictoryChartLabelsProps = LabelItem;
 
@@ -21,7 +20,7 @@ type ProcessedLine = {
  * Renders floating Skia text labels (from `<victorylabel>` nodes) over the chart canvas.
  * Intended for use inside CartesianChart's `renderOutside` callback.
  */
-function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily, lineHeight, textAnchor = 'start', verticalAnchor = 'start'}: VictoryChartLabelsProps) {
+function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily, fontStyle, lineHeight, textAnchor = 'start', verticalAnchor = 'start'}: VictoryChartLabelsProps) {
     const typefaces = useChartDefaultTypeface();
     const processedLines = text.split('\n').reduce(
         (acc, line, index) => {
@@ -29,8 +28,13 @@ function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily,
             const lineFontSize = fontSize?.[index];
             const lineFontWeight = fontWeight?.[index];
             const lineFontFamily = fontFamily?.[index];
+            const lineFontStyle = fontStyle?.[index];
             const lineLineHeight = lineHeight?.[index];
-            const typeface = getVictoryChartLabelTypeface(lineFontFamily, lineFontWeight, typefaces);
+            const typeface = getChartSkiaTypeface(typefaces, {
+                fontFamily: lineFontFamily,
+                fontStyle: lineFontStyle,
+                fontWeight: lineFontWeight,
+            });
             const lineFont = typeface && lineFontSize ? Skia.Font(typeface, lineFontSize) : null;
             const fontMetrics = lineFont?.getMetrics();
             const lineWidth = lineFont?.getGlyphWidths(lineFont.getGlyphIDs(line)).reduce((totalWidth, width) => totalWidth + width, 0) ?? 0;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
index b7de61bca20f..f9aa0ebccc78 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
@@ -1,7 +1,7 @@
 import {Circle, Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React, {Fragment} from 'react';
-import {useChartDefaultTypeface} from '@components/Charts/hooks';
+import {getChartSkiaTypeface, useChartDefaultTypeface} from '@components/Charts/hooks';
 import type {LegendItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 
 type VictoryChartLegendProps = LegendItem;
@@ -23,10 +23,10 @@ type ProcessedEntry = {
  * Intended for use inside CartesianChart's `renderOutside` callback.
  */
 function VictoryChartLegend({x, y, entries, gutter, symbolSpacer}: VictoryChartLegendProps) {
-    const {regular: regularTypeface, bold: boldTypeface} = useChartDefaultTypeface();
+    const typefaces = useChartDefaultTypeface();
     const processedEntries = entries.reduce(
-        (acc, {text, color, fontSize, fontWeight, symbolColor, symbolSize}) => {
-            const typeface = fontWeight === 'bold' ? boldTypeface : regularTypeface;
+        (acc, {text, color, fontSize, fontWeight, fontFamily, fontStyle, symbolColor, symbolSize}) => {
+            const typeface = getChartSkiaTypeface(typefaces, {fontFamily, fontStyle, fontWeight});
             const font = typeface && fontSize ? Skia.Font(typeface, fontSize) : null;
             const fontMetrics = font?.getMetrics();
             const lineHeight = fontMetrics ? fontMetrics.ascent + fontMetrics.descent + fontMetrics.leading : 0;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
index a6ee93bc96a9..057903ebb03a 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
@@ -32,8 +32,8 @@ const VictoryChartContext = createContext<VictoryChartContextValue | null>(null)
  * Returns null when the chart data is invalid (no data points, or mixed cartesian/polar content).
  */
 function VictoryChartProvider({tnode, children}: {tnode: TNode; children: React.ReactNode}) {
-    const {regular: regularTypeface} = useChartDefaultTypeface();
-    const {data, xKey, yKeys, xAxis, yAxis, domain, domainPadding, padding, isHorizontal, categories, labelItems, legendItems} = processVictoryChartTree(tnode, regularTypeface, null);
+    const typefaces = useChartDefaultTypeface();
+    const {data, xKey, yKeys, xAxis, yAxis, domain, domainPadding, padding, isHorizontal, categories, labelItems, legendItems} = processVictoryChartTree(tnode, typefaces.EXP_NEUE, null);
     const {nodeStyles: chartContentStyles, parentNodeStyles: chartContainerStyles} = parseStyles(tnode);
 
     const hasCartesianData = Object.values(data).some((entry) => X_KEY in entry);
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
index 1cb115c2d28d..e6e4f9649d79 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
@@ -14,6 +14,7 @@ function parseVictoryLabelNode(tnode: TNode): PartialProcessNodeResult {
         fontSize: {},
         fontWeight: {},
         fontFamily: {},
+        fontStyle: {},
         lineHeight: parseAttribute<number[]>(tnode.attributes.lineheight),
         textAnchor: parseAttribute<TextAnchor>(tnode.attributes.textanchor),
         verticalAnchor: parseAttribute<TextAnchor>(tnode.attributes.verticalanchor),
@@ -47,6 +48,12 @@ function parseVictoryLabelNode(tnode: TNode): PartialProcessNodeResult {
                     [index]: textStyle.fontFamily,
                 };
             }
+            if (textStyle.fontStyle) {
+                labelItem.fontStyle = {
+                    ...labelItem.fontStyle,
+                    [index]: textStyle.fontStyle,
+                };
+            }
         }
     }
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
index ebc83a9ac9fe..f7cb5bb0cd26 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
@@ -14,11 +14,13 @@ function parseVictoryLegendNode(tnode: TNode): PartialProcessNodeResult {
     const color = style?.labels?.fill;
     const fontSize = style?.labels?.fontSize !== undefined ? Number(style.labels.fontSize) : undefined;
     const fontWeight = Number(style?.labels?.fontWeight) === 700 ? 'bold' : undefined;
+    const fontFamily = style?.labels?.fontFamily;
+    const fontStyle = style?.labels?.fontStyle;
     const entries: LegendItemEntry[] = (parseAttribute<RawLegendData[]>(tnode.attributes.data) ?? []).map((entry) => {
         const text = entry.name;
         const symbolColor = entry.symbol?.fill;
         const symbolSize = entry.symbol?.size !== undefined ? Number(entry.symbol.size) : undefined;
-        return {text, color, fontSize, fontWeight, symbolColor, symbolSize};
+        return {text, color, fontSize, fontWeight, fontFamily, fontStyle, symbolColor, symbolSize};
     });
     const legendItem: LegendItem = {x, y, entries, gutter, symbolSpacer};
     return {legendItems: [legendItem]};
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
index 0b8007abfd83..4797b16ceb7b 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types.ts
@@ -37,6 +37,7 @@ type RawLabelStyle = {
     fontSize?: string | number;
     fontWeight?: string | number;
     fontFamily?: string;
+    fontStyle?: string;
 };
 
 type RawLegendStyle = {
@@ -44,6 +45,8 @@ type RawLegendStyle = {
         fill?: Color;
         fontSize?: string | number;
         fontWeight?: string | number;
+        fontFamily?: string;
+        fontStyle?: string;
     };
 };
 
@@ -85,6 +88,9 @@ type LabelItem = {
     /** Font family (per line) */
     fontFamily?: Record<number, string>;
 
+    /** Font style (per line) */
+    fontStyle?: Record<number, string>;
+
     /** Line height (per line) */
     lineHeight?: Record<number, number>;
 
@@ -108,6 +114,12 @@ type LegendItemEntry = {
     /** Font weight */
     fontWeight?: 'normal' | 'bold';
 
+    /** Font family */
+    fontFamily?: string;
+
+    /** Font style */
+    fontStyle?: string;
+
     /** The color of the symbol */
     symbolColor?: Color;
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts
deleted file mode 100644
index 62081790f762..000000000000
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getVictoryChartLabelTypeface.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type {SkTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from '@components/Charts/hooks';
-
-/** Matches `fontFamily` in Victory HTML from Web-Expensify (e.g. proactive CFO polar charts). */
-const EXPENSIFY_NEW_KANSAS_FONT_FAMILY = 'Expensify New Kansas';
-
-function getVictoryChartLabelTypeface(fontFamily: string | undefined, fontWeight: 'normal' | 'bold' | undefined, typefaces: ChartDefaultTypeface): SkTypeface | null {
-    if (fontFamily === EXPENSIFY_NEW_KANSAS_FONT_FAMILY) {
-        return typefaces.kansas;
-    }
-
-    return fontWeight === 'bold' ? typefaces.bold : typefaces.regular;
-}
-
-export default getVictoryChartLabelTypeface;

From 84b809bd3955b64a6ad0f4f88adbcb9722b600b3 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:18:22 -0700
Subject: [PATCH 10/35] Fix VictoryChartPieLabel Skia context usage

Precompute pie label config in VictoryChartPie so slice labels
do not call useVictoryChartContext inside the Skia reconciler.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../components/VictoryChartPie.tsx            | 24 +++++++++-
 .../components/VictoryChartPieLabel.tsx       | 48 +++++++++----------
 2 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 0843a623ce02..4d9d9612f3f0 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -1,7 +1,12 @@
-import React from 'react';
+import React, {useMemo} from 'react';
+import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import {Pie} from 'victory-native';
+import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
+import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
+import type {PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
+import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
 import VictoryChartPieLabel from './VictoryChartPieLabel';
 
 type VictoryChartPieProps = {tnode: TNode};
@@ -10,9 +15,21 @@ type VictoryChartPieProps = {tnode: TNode};
 const START_ANGLE = 270;
 
 function VictoryChartPie({tnode}: VictoryChartPieProps) {
+    const {data} = useVictoryChartContext();
+    const renderEngine = useAmbientTRenderEngine();
     const innerRadius = tnode.attributes.innerradius !== undefined ? Number(parseAttribute(tnode.attributes.innerradius)) : undefined;
     const radius = tnode.attributes.radius !== undefined ? Number(parseAttribute(tnode.attributes.radius)) : undefined;
     const size = radius ? radius * 2 : undefined;
+    const labelRadius = tnode.attributes.labelradius !== undefined ? Number(parseAttribute(tnode.attributes.labelradius)) : undefined;
+
+    const pieLabelConfig = useMemo(() => {
+        const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
+        const labelItemTemplate = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
+        const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
+        const labels = parseAttribute<string[]>(tnode.attributes.labels);
+
+        return {labelItemTemplate, dataLabels, labels};
+    }, [data, renderEngine, tnode.attributes.labelcomponent, tnode.attributes.labels]);
 
     return (
         <Pie.Chart
@@ -23,8 +40,11 @@ function VictoryChartPie({tnode}: VictoryChartPieProps) {
             {({slice}) => (
                 <Pie.Slice>
                     <VictoryChartPieLabel
-                        tnode={tnode}
                         slice={slice}
+                        labelItemTemplate={pieLabelConfig.labelItemTemplate}
+                        dataLabels={pieLabelConfig.dataLabels}
+                        labels={pieLabelConfig.labels}
+                        labelRadius={labelRadius}
                     />
                 </Pie.Slice>
             )}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
index a9ff0dd3ee67..9b0e6178e474 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
@@ -1,44 +1,40 @@
 import React from 'react';
-import {useAmbientTRenderEngine} from 'react-native-render-html';
-import type {TNode} from 'react-native-render-html';
 import type {PieSliceData} from 'victory-native';
-import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
-import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
-import type {PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
-import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
-import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
+import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import VictoryChartLabel from './VictoryChartLabel';
 
 type VictoryChartPieLabelProps = {
-    tnode: TNode;
     slice: PieSliceData;
+    labelItemTemplate?: LabelItem;
+    dataLabels: Array<string | number>;
+    labels?: string[];
+    labelRadius?: number;
 };
 
 const RADIAN = Math.PI / 180;
 
-function VictoryChartPieLabel({tnode, slice}: VictoryChartPieLabelProps) {
-    const {data} = useVictoryChartContext();
-    const renderEngine = useAmbientTRenderEngine();
-    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
-    const labelItem = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
-
-    if (!labelItem) {
+/**
+ * Renders a pie slice label inside the Skia canvas. Must not use React context hooks
+ * because this runs in victory-native's pie slice render callback.
+ */
+function VictoryChartPieLabel({slice, labelItemTemplate, dataLabels, labels, labelRadius}: VictoryChartPieLabelProps) {
+    if (!labelItemTemplate) {
         return null;
     }
 
-    const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
-    const labels = parseAttribute<string[]>(tnode.attributes.labels);
     const text = labels?.[dataLabels.indexOf(slice.label)] ?? slice.label;
-
-    const labelRadius = tnode.attributes.labelradius !== undefined ? Number(parseAttribute(tnode.attributes.labelradius)) : slice.radius;
+    const resolvedLabelRadius = labelRadius ?? slice.radius;
     const midAngle = (slice.startAngle + slice.endAngle) / 2;
-    const x = slice.center.x + labelRadius * Math.cos(-midAngle * RADIAN);
-    const y = slice.center.y + labelRadius * Math.sin(midAngle * RADIAN);
-
-    labelItem.text = text;
-    labelItem.x = x;
-    labelItem.y = y;
-    labelItem.textAnchor = 'middle';
+    const x = slice.center.x + resolvedLabelRadius * Math.cos(-midAngle * RADIAN);
+    const y = slice.center.y + resolvedLabelRadius * Math.sin(midAngle * RADIAN);
+
+    const labelItem: LabelItem = {
+        ...labelItemTemplate,
+        text,
+        x,
+        y,
+        textAnchor: 'middle',
+    };
 
     return <VictoryChartLabel {...labelItem} />;
 }

From bf9370aecdabf3fcb32d519f12d75f17611b1315 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:23:29 -0700
Subject: [PATCH 11/35] Remove unused chart typeface type exports for knip

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/getChartSkiaTypeface.ts | 1 -
 src/components/Charts/hooks/index.ts                  | 1 -
 2 files changed, 2 deletions(-)

diff --git a/src/components/Charts/context/getChartSkiaTypeface.ts b/src/components/Charts/context/getChartSkiaTypeface.ts
index eecf10edfa9e..b26eda1cc977 100644
--- a/src/components/Charts/context/getChartSkiaTypeface.ts
+++ b/src/components/Charts/context/getChartSkiaTypeface.ts
@@ -56,4 +56,3 @@ function getChartSkiaTypeface(
 }
 
 export {getChartSkiaTypeface};
-export type {ChartLabelFontStyle, ChartLabelFontWeight};
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index f84d2716cbb8..26ea730a2346 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -6,7 +6,6 @@ export {default as useChartFontManager} from './useChartFontManager/useChartFont
 export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
 export {getChartSkiaTypeface} from '../context/getChartSkiaTypeface';
 export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
-export type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '../context/chartSkiaTypefaceTypes';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';

From 363c4bdf39c7e5b8cd965331846a62f6359783fb Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:30:27 -0700
Subject: [PATCH 12/35] Reorganize Charts module into utils, types, and hooks
 folders.

Move chart helpers out of context so providers only own React context wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/ChartDefaultTypefaceContext.tsx | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.native.tsx    | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.tsx           | 2 +-
 src/components/Charts/hooks/index.ts                          | 2 +-
 .../Charts/hooks/useChartFontManager/useChartFontManager.ts   | 2 +-
 .../Charts/{context => hooks}/useChartSkiaTypefaces.native.ts | 2 +-
 .../Charts/{context => hooks}/useChartSkiaTypefaces.ts        | 4 ++--
 .../Charts/{context => types}/chartSkiaTypefaceTypes.ts       | 0
 src/components/Charts/{types.ts => types/index.ts}            | 2 +-
 src/components/Charts/{context => utils}/chartWebFont.ts      | 0
 .../Charts/{context => utils}/getChartSkiaTypeface.ts         | 2 +-
 src/components/Charts/{utils.ts => utils/index.ts}            | 4 ++--
 12 files changed, 12 insertions(+), 12 deletions(-)
 rename src/components/Charts/{context => hooks}/useChartSkiaTypefaces.native.ts (95%)
 rename src/components/Charts/{context => hooks}/useChartSkiaTypefaces.ts (92%)
 rename src/components/Charts/{context => types}/chartSkiaTypefaceTypes.ts (100%)
 rename src/components/Charts/{types.ts => types/index.ts} (97%)
 rename src/components/Charts/{context => utils}/chartWebFont.ts (100%)
 rename src/components/Charts/{context => utils}/getChartSkiaTypeface.ts (98%)
 rename src/components/Charts/{utils.ts => utils/index.ts} (99%)

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index 6a0e783d8354..de54a4b00468 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -1,5 +1,5 @@
 import {createContext, useContext} from 'react';
-import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
+import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
 
 const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
 
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index 0a7384e5a622..29e1010b1e04 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
+import useChartSkiaTypefaces from '../hooks/useChartSkiaTypefaces';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
-import useChartSkiaTypefaces from './useChartSkiaTypefaces';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const typefaces = useChartSkiaTypefaces();
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index 0a7384e5a622..29e1010b1e04 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
+import useChartSkiaTypefaces from '../hooks/useChartSkiaTypefaces';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
-import useChartSkiaTypefaces from './useChartSkiaTypefaces';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
     const typefaces = useChartSkiaTypefaces();
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 26ea730a2346..f2dc08caf82d 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,7 +4,7 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
 export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
-export {getChartSkiaTypeface} from '../context/getChartSkiaTypeface';
+export {getChartSkiaTypeface} from '../utils/getChartSkiaTypeface';
 export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
index f43896f3fb14..3b11301f4646 100644
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
+++ b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
@@ -1,6 +1,6 @@
 import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
 import {useFonts} from '@shopify/react-native-skia';
-import chartWebFont from '@components/Charts/context/chartWebFont';
+import chartWebFont from '@components/Charts/utils/chartWebFont';
 
 function useChartFontManager(): SkTypefaceFontProvider | null {
     return useFonts({
diff --git a/src/components/Charts/context/useChartSkiaTypefaces.native.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
similarity index 95%
rename from src/components/Charts/context/useChartSkiaTypefaces.native.ts
rename to src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
index 9f47af1236fd..551b095ec4d6 100644
--- a/src/components/Charts/context/useChartSkiaTypefaces.native.ts
+++ b/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
@@ -1,6 +1,6 @@
 import type {DataModule} from '@shopify/react-native-skia';
 import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
+import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
 
 function useChartSkiaTypefaces(): ChartDefaultTypeface {
     return {
diff --git a/src/components/Charts/context/useChartSkiaTypefaces.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.ts
similarity index 92%
rename from src/components/Charts/context/useChartSkiaTypefaces.ts
rename to src/components/Charts/hooks/useChartSkiaTypefaces.ts
index 1125675aa155..16f6357cf5e4 100644
--- a/src/components/Charts/context/useChartSkiaTypefaces.ts
+++ b/src/components/Charts/hooks/useChartSkiaTypefaces.ts
@@ -1,6 +1,6 @@
 import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
-import chartWebFont from './chartWebFont';
+import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
+import chartWebFont from '../utils/chartWebFont';
 
 function useChartSkiaTypefaces(): ChartDefaultTypeface {
     return {
diff --git a/src/components/Charts/context/chartSkiaTypefaceTypes.ts b/src/components/Charts/types/chartSkiaTypefaceTypes.ts
similarity index 100%
rename from src/components/Charts/context/chartSkiaTypefaceTypes.ts
rename to src/components/Charts/types/chartSkiaTypefaceTypes.ts
diff --git a/src/components/Charts/types.ts b/src/components/Charts/types/index.ts
similarity index 97%
rename from src/components/Charts/types.ts
rename to src/components/Charts/types/index.ts
index 2ed86d19ec06..dc3de15d5bb8 100644
--- a/src/components/Charts/types.ts
+++ b/src/components/Charts/types/index.ts
@@ -1,6 +1,6 @@
 import type {SkParagraph} from '@shopify/react-native-skia';
 import type {ValueOf} from 'type-fest';
-import type {LABEL_ROTATIONS} from './VictoryTheme';
+import type {LABEL_ROTATIONS} from '../VictoryTheme';
 
 type ChartDataPoint = {
     /** Label displayed under the data point (e.g., "Amazon", "Nov 2025") */
diff --git a/src/components/Charts/context/chartWebFont.ts b/src/components/Charts/utils/chartWebFont.ts
similarity index 100%
rename from src/components/Charts/context/chartWebFont.ts
rename to src/components/Charts/utils/chartWebFont.ts
diff --git a/src/components/Charts/context/getChartSkiaTypeface.ts b/src/components/Charts/utils/getChartSkiaTypeface.ts
similarity index 98%
rename from src/components/Charts/context/getChartSkiaTypeface.ts
rename to src/components/Charts/utils/getChartSkiaTypeface.ts
index b26eda1cc977..57a96179c9f3 100644
--- a/src/components/Charts/context/getChartSkiaTypeface.ts
+++ b/src/components/Charts/utils/getChartSkiaTypeface.ts
@@ -2,7 +2,7 @@ import type {SkTypeface} from '@shopify/react-native-skia';
 import type {TextStyle} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
-import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from './chartSkiaTypefaceTypes';
+import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '../types/chartSkiaTypefaceTypes';
 
 type ChartLabelFontStyle = 'normal' | 'italic';
 type ChartLabelFontWeight = 'normal' | 'bold';
diff --git a/src/components/Charts/utils.ts b/src/components/Charts/utils/index.ts
similarity index 99%
rename from src/components/Charts/utils.ts
rename to src/components/Charts/utils/index.ts
index 6db9a4455453..6990ddc4ba95 100644
--- a/src/components/Charts/utils.ts
+++ b/src/components/Charts/utils/index.ts
@@ -1,8 +1,8 @@
 import {FontStyle, FontWeight, Skia} from '@shopify/react-native-skia';
 import type {SkParagraph, SkParagraphBuilder, SkTypefaceFontProvider} from '@shopify/react-native-skia';
 import variables from '@styles/variables';
-import type {ChartDataPoint, LabelRotation, PieSlice} from './types';
-import VictoryTheme, {DIAGONAL_ANGLE_RADIAN_THRESHOLD, ELLIPSIS, LABEL_PADDING, LABEL_ROTATIONS, MAX_X_AXIS_LABEL_WIDTH, SIN_45} from './VictoryTheme';
+import type {ChartDataPoint, LabelRotation, PieSlice} from '../types';
+import VictoryTheme, {DIAGONAL_ANGLE_RADIAN_THRESHOLD, ELLIPSIS, LABEL_PADDING, LABEL_ROTATIONS, MAX_X_AXIS_LABEL_WIDTH, SIN_45} from '../VictoryTheme';
 
 /** One reusable ParagraphBuilder per fontMgr instance. Auto-GC'd when fontMgr is released. */
 const builderCache = new WeakMap<SkTypefaceFontProvider, SkParagraphBuilder>();

From db3b35b593d6b89a93e4e8a88248604dd93cdd34 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 09:35:58 -0700
Subject: [PATCH 13/35] Fix Charts import aliases and default export for lint

Use @components paths per prefer-alias and default-export getChartSkiaTypeface.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/context/ChartDefaultTypefaceContext.tsx          | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.native.tsx  | 2 +-
 .../Charts/context/ChartDefaultTypefaceProvider.tsx         | 2 +-
 src/components/Charts/hooks/index.ts                        | 6 +++---
 src/components/Charts/hooks/useChartSkiaTypefaces.native.ts | 2 +-
 src/components/Charts/hooks/useChartSkiaTypefaces.ts        | 4 ++--
 src/components/Charts/types/index.ts                        | 2 +-
 src/components/Charts/utils/getChartSkiaTypeface.ts         | 4 ++--
 src/components/Charts/utils/index.ts                        | 4 ++--
 9 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
index de54a4b00468..6ffdb6ac6037 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
@@ -1,5 +1,5 @@
 import {createContext, useContext} from 'react';
-import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
+import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
 
 const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
 
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
index 29e1010b1e04..a7fbfdc14df0 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import useChartSkiaTypefaces from '../hooks/useChartSkiaTypefaces';
+import useChartSkiaTypefaces from '@components/Charts/hooks/useChartSkiaTypefaces';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
index 29e1010b1e04..a7fbfdc14df0 100644
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import useChartSkiaTypefaces from '../hooks/useChartSkiaTypefaces';
+import useChartSkiaTypefaces from '@components/Charts/hooks/useChartSkiaTypefaces';
 import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
 
 function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index f2dc08caf82d..3c9ebaaf64e9 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -3,9 +3,9 @@ export {default as useChartLabelMeasurements} from './useChartLabelMeasurements'
 export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
-export {default as ChartDefaultTypefaceProvider} from '../context/ChartDefaultTypefaceProvider';
-export {getChartSkiaTypeface} from '../utils/getChartSkiaTypeface';
-export {useChartDefaultTypeface} from '../context/ChartDefaultTypefaceContext';
+export {default as ChartDefaultTypefaceProvider} from '@components/Charts/context/ChartDefaultTypefaceProvider';
+export {default as getChartSkiaTypeface} from '@components/Charts/utils/getChartSkiaTypeface';
+export {useChartDefaultTypeface} from '@components/Charts/context/ChartDefaultTypefaceContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';
diff --git a/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
index 551b095ec4d6..f74cbfb96189 100644
--- a/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
+++ b/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
@@ -1,6 +1,6 @@
 import type {DataModule} from '@shopify/react-native-skia';
 import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
+import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
 
 function useChartSkiaTypefaces(): ChartDefaultTypeface {
     return {
diff --git a/src/components/Charts/hooks/useChartSkiaTypefaces.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.ts
index 16f6357cf5e4..aa95fe01dac0 100644
--- a/src/components/Charts/hooks/useChartSkiaTypefaces.ts
+++ b/src/components/Charts/hooks/useChartSkiaTypefaces.ts
@@ -1,6 +1,6 @@
 import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from '../types/chartSkiaTypefaceTypes';
-import chartWebFont from '../utils/chartWebFont';
+import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import chartWebFont from '@components/Charts/utils/chartWebFont';
 
 function useChartSkiaTypefaces(): ChartDefaultTypeface {
     return {
diff --git a/src/components/Charts/types/index.ts b/src/components/Charts/types/index.ts
index dc3de15d5bb8..b44954eb31e4 100644
--- a/src/components/Charts/types/index.ts
+++ b/src/components/Charts/types/index.ts
@@ -1,6 +1,6 @@
 import type {SkParagraph} from '@shopify/react-native-skia';
 import type {ValueOf} from 'type-fest';
-import type {LABEL_ROTATIONS} from '../VictoryTheme';
+import type {LABEL_ROTATIONS} from '@components/Charts/VictoryTheme';
 
 type ChartDataPoint = {
     /** Label displayed under the data point (e.g., "Amazon", "Nov 2025") */
diff --git a/src/components/Charts/utils/getChartSkiaTypeface.ts b/src/components/Charts/utils/getChartSkiaTypeface.ts
index 57a96179c9f3..4b62a0c34a2c 100644
--- a/src/components/Charts/utils/getChartSkiaTypeface.ts
+++ b/src/components/Charts/utils/getChartSkiaTypeface.ts
@@ -1,8 +1,8 @@
 import type {SkTypeface} from '@shopify/react-native-skia';
 import type {TextStyle} from 'react-native';
+import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
 // eslint-disable-next-line no-restricted-imports
 import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
-import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '../types/chartSkiaTypefaceTypes';
 
 type ChartLabelFontStyle = 'normal' | 'italic';
 type ChartLabelFontWeight = 'normal' | 'bold';
@@ -55,4 +55,4 @@ function getChartSkiaTypeface(
     return typefaces[typefaceKey];
 }
 
-export {getChartSkiaTypeface};
+export default getChartSkiaTypeface;
diff --git a/src/components/Charts/utils/index.ts b/src/components/Charts/utils/index.ts
index 6990ddc4ba95..9fa433e9e6c6 100644
--- a/src/components/Charts/utils/index.ts
+++ b/src/components/Charts/utils/index.ts
@@ -1,8 +1,8 @@
 import {FontStyle, FontWeight, Skia} from '@shopify/react-native-skia';
 import type {SkParagraph, SkParagraphBuilder, SkTypefaceFontProvider} from '@shopify/react-native-skia';
+import type {ChartDataPoint, LabelRotation, PieSlice} from '@components/Charts/types';
+import VictoryTheme, {DIAGONAL_ANGLE_RADIAN_THRESHOLD, ELLIPSIS, LABEL_PADDING, LABEL_ROTATIONS, MAX_X_AXIS_LABEL_WIDTH, SIN_45} from '@components/Charts/VictoryTheme';
 import variables from '@styles/variables';
-import type {ChartDataPoint, LabelRotation, PieSlice} from '../types';
-import VictoryTheme, {DIAGONAL_ANGLE_RADIAN_THRESHOLD, ELLIPSIS, LABEL_PADDING, LABEL_ROTATIONS, MAX_X_AXIS_LABEL_WIDTH, SIN_45} from '../VictoryTheme';
 
 /** One reusable ParagraphBuilder per fontMgr instance. Auto-GC'd when fontMgr is released. */
 const builderCache = new WeakMap<SkTypefaceFontProvider, SkParagraphBuilder>();

From 0ec3e1596941c0301a2985f632d18ab33b6daf8e Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:50:16 -0700
Subject: [PATCH 14/35] chore(charts): add chart font registry and loader cache

Centralize font asset paths and single-flight Skia loading so nested
chart providers share one decoded fontMgr and typeface map.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/types/chartFontsTypes.ts           |  11 ++
 .../Charts/utils/chartFontAssets.native.ts    |  40 ++++++
 .../Charts/utils/chartFontAssets.ts           |  41 ++++++
 .../Charts/utils/chartFontsCache.ts           | 120 ++++++++++++++++++
 .../Charts/utils/createChartFontsValue.ts     |  18 +++
 5 files changed, 230 insertions(+)
 create mode 100644 src/components/Charts/types/chartFontsTypes.ts
 create mode 100644 src/components/Charts/utils/chartFontAssets.native.ts
 create mode 100644 src/components/Charts/utils/chartFontAssets.ts
 create mode 100644 src/components/Charts/utils/chartFontsCache.ts
 create mode 100644 src/components/Charts/utils/createChartFontsValue.ts

diff --git a/src/components/Charts/types/chartFontsTypes.ts b/src/components/Charts/types/chartFontsTypes.ts
new file mode 100644
index 000000000000..eb55f5ad8c38
--- /dev/null
+++ b/src/components/Charts/types/chartFontsTypes.ts
@@ -0,0 +1,11 @@
+import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
+import type {ChartDefaultTypeface} from './chartSkiaTypefaceTypes';
+
+type ChartFontsValue = {
+    typefaces: ChartDefaultTypeface;
+    fontMgr: SkTypefaceFontProvider | null;
+};
+
+type ChartFontsContextValue = ChartFontsValue;
+
+export type {ChartFontsContextValue, ChartFontsValue};
diff --git a/src/components/Charts/utils/chartFontAssets.native.ts b/src/components/Charts/utils/chartFontAssets.native.ts
new file mode 100644
index 000000000000..ba261225a1b4
--- /dev/null
+++ b/src/components/Charts/utils/chartFontAssets.native.ts
@@ -0,0 +1,40 @@
+import type {DataModule} from '@shopify/react-native-skia';
+import type {ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+
+const EXPENSIFY_MONO_REGULAR = require('@assets/fonts/native/ExpensifyMono-Regular.otf') as DataModule;
+const EXPENSIFY_MONO_BOLD = require('@assets/fonts/native/ExpensifyMono-Bold.otf') as DataModule;
+const EXPENSIFY_MONO_ITALIC = require('@assets/fonts/native/ExpensifyMono-Italic.otf') as DataModule;
+const EXPENSIFY_MONO_BOLD_ITALIC = require('@assets/fonts/native/ExpensifyMono-BoldItalic.otf') as DataModule;
+const EXPENSIFY_NEUE_REGULAR = require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule;
+const EXPENSIFY_NEUE_BOLD = require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule;
+const EXPENSIFY_NEUE_ITALIC = require('@assets/fonts/native/ExpensifyNeue-Italic.otf') as DataModule;
+const EXPENSIFY_NEUE_BOLD_ITALIC = require('@assets/fonts/native/ExpensifyNeue-BoldItalic.otf') as DataModule;
+const EXPENSIFY_NEW_KANSAS_MEDIUM = require('@assets/fonts/native/ExpensifyNewKansas-Medium.otf') as DataModule;
+const EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC = require('@assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf') as DataModule;
+const CUSTOM_EMOJI_FONT = require('@assets/fonts/native/CustomEmojiNativeFont.ttf') as DataModule;
+const SANS_SYMBOLS_FONT = require('@assets/fonts/NotoSans-Symbols.ttf') as DataModule;
+const SANS_SC_MONTHS_FONT = require('@assets/fonts/NotoSansSC-Months.ttf') as DataModule;
+
+const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
+    MONOSPACE: EXPENSIFY_MONO_REGULAR,
+    MONOSPACE_BOLD: EXPENSIFY_MONO_BOLD,
+    MONOSPACE_ITALIC: EXPENSIFY_MONO_ITALIC,
+    MONOSPACE_BOLD_ITALIC: EXPENSIFY_MONO_BOLD_ITALIC,
+    EXP_NEUE: EXPENSIFY_NEUE_REGULAR,
+    EXP_NEUE_BOLD: EXPENSIFY_NEUE_BOLD,
+    EXP_NEUE_ITALIC: EXPENSIFY_NEUE_ITALIC,
+    EXP_NEUE_BOLD_ITALIC: EXPENSIFY_NEUE_BOLD_ITALIC,
+    EXP_NEW_KANSAS_MEDIUM: EXPENSIFY_NEW_KANSAS_MEDIUM,
+    EXP_NEW_KANSAS_MEDIUM_ITALIC: EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC,
+    CUSTOM_EMOJI_FONT,
+};
+
+const CHART_FONT_MANAGER_FAMILIES: Record<string, DataModule[]> = {
+    ExpensifyNeue: [EXPENSIFY_NEUE_REGULAR, EXPENSIFY_NEUE_BOLD, EXPENSIFY_NEUE_ITALIC, EXPENSIFY_NEUE_BOLD_ITALIC],
+    ExpensifyMono: [EXPENSIFY_MONO_REGULAR, EXPENSIFY_MONO_BOLD, EXPENSIFY_MONO_ITALIC, EXPENSIFY_MONO_BOLD_ITALIC],
+    ExpensifyNewKansas: [EXPENSIFY_NEW_KANSAS_MEDIUM, EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC],
+    NotoSansSymbols: [SANS_SYMBOLS_FONT],
+    NotoSansSCMonths: [SANS_SC_MONTHS_FONT],
+};
+
+export {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontAssets.ts b/src/components/Charts/utils/chartFontAssets.ts
new file mode 100644
index 000000000000..26be32f9bbd9
--- /dev/null
+++ b/src/components/Charts/utils/chartFontAssets.ts
@@ -0,0 +1,41 @@
+import type {DataModule} from '@shopify/react-native-skia';
+import type {ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import chartWebFont from './chartWebFont';
+
+const EXPENSIFY_MONO_REGULAR = chartWebFont(require('@assets/fonts/web/ExpensifyMono-Regular.woff2') as string);
+const EXPENSIFY_MONO_BOLD = chartWebFont(require('@assets/fonts/web/ExpensifyMono-Bold.woff2') as string);
+const EXPENSIFY_MONO_ITALIC = chartWebFont(require('@assets/fonts/web/ExpensifyMono-Italic.woff2') as string);
+const EXPENSIFY_MONO_BOLD_ITALIC = chartWebFont(require('@assets/fonts/web/ExpensifyMono-BoldItalic.woff2') as string);
+const EXPENSIFY_NEUE_REGULAR = chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string);
+const EXPENSIFY_NEUE_BOLD = chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string);
+const EXPENSIFY_NEUE_ITALIC = chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string);
+const EXPENSIFY_NEUE_BOLD_ITALIC = chartWebFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string);
+const EXPENSIFY_NEW_KANSAS_MEDIUM = chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-Medium.woff2') as string);
+const EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC = chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-MediumItalic.woff2') as string);
+const CUSTOM_EMOJI_FONT = chartWebFont(require('@assets/fonts/web/CustomEmojiWebFont.ttf') as string);
+const SANS_SYMBOLS_FONT = chartWebFont(require('@assets/fonts/NotoSans-Symbols.ttf') as string);
+const SANS_SC_MONTHS_FONT = chartWebFont(require('@assets/fonts/NotoSansSC-Months.ttf') as string);
+
+const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
+    MONOSPACE: EXPENSIFY_MONO_REGULAR,
+    MONOSPACE_BOLD: EXPENSIFY_MONO_BOLD,
+    MONOSPACE_ITALIC: EXPENSIFY_MONO_ITALIC,
+    MONOSPACE_BOLD_ITALIC: EXPENSIFY_MONO_BOLD_ITALIC,
+    EXP_NEUE: EXPENSIFY_NEUE_REGULAR,
+    EXP_NEUE_BOLD: EXPENSIFY_NEUE_BOLD,
+    EXP_NEUE_ITALIC: EXPENSIFY_NEUE_ITALIC,
+    EXP_NEUE_BOLD_ITALIC: EXPENSIFY_NEUE_BOLD_ITALIC,
+    EXP_NEW_KANSAS_MEDIUM: EXPENSIFY_NEW_KANSAS_MEDIUM,
+    EXP_NEW_KANSAS_MEDIUM_ITALIC: EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC,
+    CUSTOM_EMOJI_FONT,
+};
+
+const CHART_FONT_MANAGER_FAMILIES: Record<string, DataModule[]> = {
+    ExpensifyNeue: [EXPENSIFY_NEUE_REGULAR, EXPENSIFY_NEUE_BOLD, EXPENSIFY_NEUE_ITALIC, EXPENSIFY_NEUE_BOLD_ITALIC],
+    ExpensifyMono: [EXPENSIFY_MONO_REGULAR, EXPENSIFY_MONO_BOLD, EXPENSIFY_MONO_ITALIC, EXPENSIFY_MONO_BOLD_ITALIC],
+    ExpensifyNewKansas: [EXPENSIFY_NEW_KANSAS_MEDIUM, EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC],
+    NotoSansSymbols: [SANS_SYMBOLS_FONT],
+    NotoSansSCMonths: [SANS_SC_MONTHS_FONT],
+};
+
+export {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontsCache.ts b/src/components/Charts/utils/chartFontsCache.ts
new file mode 100644
index 000000000000..62874391ca85
--- /dev/null
+++ b/src/components/Charts/utils/chartFontsCache.ts
@@ -0,0 +1,120 @@
+import type {DataModule, SkTypeface} from '@shopify/react-native-skia';
+import {Skia} from '@shopify/react-native-skia';
+import {Image} from 'react-native';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
+import createChartFontsValue from './createChartFontsValue';
+
+const EMPTY_CHART_FONTS: ChartFontsValue = {
+    typefaces: {
+        MONOSPACE: null,
+        MONOSPACE_BOLD: null,
+        MONOSPACE_ITALIC: null,
+        MONOSPACE_BOLD_ITALIC: null,
+        EXP_NEUE: null,
+        EXP_NEUE_BOLD: null,
+        EXP_NEUE_ITALIC: null,
+        EXP_NEUE_BOLD_ITALIC: null,
+        EXP_NEW_KANSAS_MEDIUM: null,
+        EXP_NEW_KANSAS_MEDIUM_ITALIC: null,
+        CUSTOM_EMOJI_FONT: null,
+    },
+    fontMgr: null,
+};
+
+let cachedChartFonts: ChartFontsValue | null = null;
+let loadPromise: Promise<ChartFontsValue> | null = null;
+const chartFontLoadListeners = new Set<() => void>();
+
+function resolveChartFontAsset(source: DataModule | string): string {
+    if (typeof source === 'string') {
+        return source;
+    }
+
+    if (typeof source === 'number') {
+        return Image.resolveAssetSource(source).uri;
+    }
+
+    if ('default' in source && typeof source.default === 'string') {
+        return source.default;
+    }
+
+    if ('uri' in source && typeof source.uri === 'string') {
+        return source.uri;
+    }
+
+    throw new Error('Unsupported chart font asset source');
+}
+
+function loadTypefaceFromAsset(source: DataModule | string): Promise<SkTypeface | null> {
+    const uri = resolveChartFontAsset(source);
+
+    return Skia.Data.fromURI(uri).then((data) => Skia.Typeface.MakeFreeTypeFaceFromData(data));
+}
+
+function loadChartSkiaTypefaces(): Promise<ChartDefaultTypeface> {
+    const typefaceKeys = Object.keys(CHART_SKIA_TYPEFACE_ASSETS) as ChartSkiaTypefaceKey[];
+
+    return Promise.all(
+        typefaceKeys.map(async (typefaceKey) => {
+            const typeface = await loadTypefaceFromAsset(CHART_SKIA_TYPEFACE_ASSETS[typefaceKey]);
+            return [typefaceKey, typeface] as const;
+        }),
+    ).then((entries) => Object.fromEntries(entries) as ChartDefaultTypeface);
+}
+
+function loadFontManagerFamilies(): Promise<Record<string, SkTypeface[]>> {
+    return Promise.all(
+        Object.entries(CHART_FONT_MANAGER_FAMILIES).map(async ([familyName, familyAssets]) => {
+            const familyTypefaces = await Promise.all(familyAssets.map((asset) => loadTypefaceFromAsset(asset)));
+
+            return [familyName, familyTypefaces.filter((typeface): typeface is SkTypeface => typeface !== null)] as const;
+        }),
+    ).then((entries) => Object.fromEntries(entries));
+}
+
+function loadChartFonts(): Promise<ChartFontsValue> {
+    return Promise.all([loadChartSkiaTypefaces(), loadFontManagerFamilies()]).then(([typefaces, fontManagerFamilies]) => createChartFontsValue(typefaces, fontManagerFamilies));
+}
+
+function subscribeToChartFonts(listener: () => void): () => void {
+    chartFontLoadListeners.add(listener);
+
+    return () => {
+        chartFontLoadListeners.delete(listener);
+    };
+}
+
+function getChartFontsSnapshot(): ChartFontsValue {
+    return cachedChartFonts ?? EMPTY_CHART_FONTS;
+}
+
+function notifyChartFontLoadListeners(): void {
+    for (const listener of chartFontLoadListeners) {
+        listener();
+    }
+}
+
+function loadChartFontsOnce(): Promise<ChartFontsValue> {
+    if (cachedChartFonts) {
+        return Promise.resolve(cachedChartFonts);
+    }
+
+    if (!loadPromise) {
+        loadPromise = loadChartFonts()
+            .then((fonts) => {
+                cachedChartFonts = fonts;
+                notifyChartFontLoadListeners();
+                return fonts;
+            })
+            .catch((error) => {
+                loadPromise = null;
+                throw error;
+            });
+    }
+
+    return loadPromise;
+}
+
+export {getChartFontsSnapshot, loadChartFontsOnce, subscribeToChartFonts};
diff --git a/src/components/Charts/utils/createChartFontsValue.ts b/src/components/Charts/utils/createChartFontsValue.ts
new file mode 100644
index 000000000000..5b5154f82030
--- /dev/null
+++ b/src/components/Charts/utils/createChartFontsValue.ts
@@ -0,0 +1,18 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+import {Skia} from '@shopify/react-native-skia';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
+
+function createChartFontsValue(typefaces: ChartDefaultTypeface, fontManagerFamilies: Record<string, SkTypeface[]>): ChartFontsValue {
+    const fontMgr = Skia.TypefaceFontProvider.Make();
+
+    for (const [familyName, familyTypefaces] of Object.entries(fontManagerFamilies)) {
+        for (const typeface of familyTypefaces) {
+            fontMgr.registerFont(typeface, familyName);
+        }
+    }
+
+    return {typefaces, fontMgr};
+}
+
+export default createChartFontsValue;

From 43d3f0141ea71f1c2bf6b87a0725228567e115af Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:50:20 -0700
Subject: [PATCH 15/35] feat(charts): add ChartFontsProvider and context hooks

Introduce ChartFontsProvider with injectable value for future CLI use,
plus useChartTypefaces while legacy loaders remain for existing call sites.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/context/ChartFontsContext.tsx      | 26 +++++++++++++++++
 .../context/ChartFontsProvider.native.tsx     | 29 +++++++++++++++++++
 .../Charts/context/ChartFontsProvider.tsx     | 29 +++++++++++++++++++
 src/components/Charts/hooks/index.ts          |  2 ++
 src/components/Charts/hooks/useChartFonts.ts  | 17 +++++++++++
 5 files changed, 103 insertions(+)
 create mode 100644 src/components/Charts/context/ChartFontsContext.tsx
 create mode 100644 src/components/Charts/context/ChartFontsProvider.native.tsx
 create mode 100644 src/components/Charts/context/ChartFontsProvider.tsx
 create mode 100644 src/components/Charts/hooks/useChartFonts.ts

diff --git a/src/components/Charts/context/ChartFontsContext.tsx b/src/components/Charts/context/ChartFontsContext.tsx
new file mode 100644
index 000000000000..56ef96b9511c
--- /dev/null
+++ b/src/components/Charts/context/ChartFontsContext.tsx
@@ -0,0 +1,26 @@
+import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
+import {createContext, useContext} from 'react';
+import type {ChartFontsContextValue} from '@components/Charts/types/chartFontsTypes';
+import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
+
+const ChartFontsContext = createContext<ChartFontsContextValue | null>(null);
+
+function useChartFontsContext(): ChartFontsContextValue {
+    const context = useContext(ChartFontsContext);
+
+    if (!context) {
+        throw new Error('Chart fonts hooks must be used within ChartFontsProvider');
+    }
+
+    return context;
+}
+
+function useChartTypefaces(): ChartDefaultTypeface {
+    return useChartFontsContext().typefaces;
+}
+
+function useChartFontManager(): SkTypefaceFontProvider | null {
+    return useChartFontsContext().fontMgr;
+}
+
+export {ChartFontsContext, useChartFontManager, useChartTypefaces};
diff --git a/src/components/Charts/context/ChartFontsProvider.native.tsx b/src/components/Charts/context/ChartFontsProvider.native.tsx
new file mode 100644
index 000000000000..4f98b3223b4f
--- /dev/null
+++ b/src/components/Charts/context/ChartFontsProvider.native.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import useChartFonts from '@components/Charts/hooks/useChartFonts';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import {ChartFontsContext} from './ChartFontsContext';
+
+type ChartFontsProviderProps = {
+    children: React.ReactNode;
+    value?: ChartFontsValue;
+};
+
+function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
+    const fonts = useChartFonts();
+
+    return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
+}
+
+ChartFontsLoaderProvider.displayName = 'ChartFontsLoaderProvider';
+
+function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
+    if (value) {
+        return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
+    }
+
+    return <ChartFontsLoaderProvider>{children}</ChartFontsLoaderProvider>;
+}
+
+ChartFontsProvider.displayName = 'ChartFontsProvider';
+
+export default ChartFontsProvider;
diff --git a/src/components/Charts/context/ChartFontsProvider.tsx b/src/components/Charts/context/ChartFontsProvider.tsx
new file mode 100644
index 000000000000..4f98b3223b4f
--- /dev/null
+++ b/src/components/Charts/context/ChartFontsProvider.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import useChartFonts from '@components/Charts/hooks/useChartFonts';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import {ChartFontsContext} from './ChartFontsContext';
+
+type ChartFontsProviderProps = {
+    children: React.ReactNode;
+    value?: ChartFontsValue;
+};
+
+function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
+    const fonts = useChartFonts();
+
+    return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
+}
+
+ChartFontsLoaderProvider.displayName = 'ChartFontsLoaderProvider';
+
+function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
+    if (value) {
+        return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
+    }
+
+    return <ChartFontsLoaderProvider>{children}</ChartFontsLoaderProvider>;
+}
+
+ChartFontsProvider.displayName = 'ChartFontsProvider';
+
+export default ChartFontsProvider;
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 3c9ebaaf64e9..83685f385cd3 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,8 +4,10 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
 export {default as ChartDefaultTypefaceProvider} from '@components/Charts/context/ChartDefaultTypefaceProvider';
+export {default as ChartFontsProvider} from '@components/Charts/context/ChartFontsProvider';
 export {default as getChartSkiaTypeface} from '@components/Charts/utils/getChartSkiaTypeface';
 export {useChartDefaultTypeface} from '@components/Charts/context/ChartDefaultTypefaceContext';
+export {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
 export {default as useChartLabelFormats} from './useChartLabelFormats';
diff --git a/src/components/Charts/hooks/useChartFonts.ts b/src/components/Charts/hooks/useChartFonts.ts
new file mode 100644
index 000000000000..365cc260206b
--- /dev/null
+++ b/src/components/Charts/hooks/useChartFonts.ts
@@ -0,0 +1,17 @@
+import {useEffect, useSyncExternalStore} from 'react';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import {getChartFontsSnapshot, loadChartFontsOnce, subscribeToChartFonts} from '@components/Charts/utils/chartFontsCache';
+
+function useChartFonts(): ChartFontsValue {
+    const fonts = useSyncExternalStore(subscribeToChartFonts, getChartFontsSnapshot, getChartFontsSnapshot);
+
+    useEffect(() => {
+        loadChartFontsOnce().catch(() => {
+            // Chart consumers null-guard fontMgr and typefaces until load succeeds.
+        });
+    }, []);
+
+    return fonts;
+}
+
+export default useChartFonts;

From a99499f16eadb5e5b3b8c18f8c6a28754a33c2d6 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:50:27 -0700
Subject: [PATCH 16/35] refactor(charts): migrate Victory HTML to
 ChartFontsProvider

Switch Victory chart renderers to ChartFontsProvider and useChartTypefaces
while Bar/Line charts continue using the legacy font hooks.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../VictoryChartRenderer/BaseVictoryChartRenderer.tsx       | 6 +++---
 .../components/VictoryChartCartesian.tsx                    | 6 +++---
 .../VictoryChartRenderer/components/VictoryChartLabel.tsx   | 4 ++--
 .../VictoryChartRenderer/components/VictoryChartLegend.tsx  | 4 ++--
 .../VictoryChartRenderer/components/VictoryChartPolar.tsx   | 6 +++---
 .../VictoryChartRenderer/context/VictoryChartContext.tsx    | 4 ++--
 6 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
index a84bb3b17166..e058d52f31b2 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import VictoryChartContainer from './components/VictoryChartContainer';
 import VictoryChartContent from './components/VictoryChartContent';
 import {VictoryChartProvider} from './context/VictoryChartContext';
@@ -7,13 +7,13 @@ import type {VictoryChartRendererProps} from './types';
 
 function BaseVictoryChartRenderer({tnode}: VictoryChartRendererProps) {
     return (
-        <ChartDefaultTypefaceProvider>
+        <ChartFontsProvider>
             <VictoryChartProvider tnode={tnode}>
                 <VictoryChartContainer>
                     <VictoryChartContent />
                 </VictoryChartContainer>
             </VictoryChartProvider>
-        </ChartDefaultTypefaceProvider>
+        </ChartFontsProvider>
     );
 }
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 2beed92cd08f..e6ed3e7232a9 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
-import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -26,7 +26,7 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <ChartDefaultTypefaceProvider>
+                <ChartFontsProvider>
                     <VictoryChartRenderArgsProvider value={renderArgs}>
                         {labelItems.map((labelItem) => (
                             <VictoryChartLabel
@@ -41,7 +41,7 @@ function VictoryChartCartesian() {
                             />
                         ))}
                     </VictoryChartRenderArgsProvider>
-                </ChartDefaultTypefaceProvider>
+                </ChartFontsProvider>
             )}
         >
             {(renderArgs) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
index d59e52873a6c..0efc95c9e0af 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
@@ -1,7 +1,7 @@
 import {Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React from 'react';
-import {getChartSkiaTypeface, useChartDefaultTypeface} from '@components/Charts/hooks';
+import {getChartSkiaTypeface, useChartTypefaces} from '@components/Charts/hooks';
 import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import computeTextAnchorPosition from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/computeTextAnchorPosition';
 
@@ -21,7 +21,7 @@ type ProcessedLine = {
  * Intended for use inside CartesianChart's `renderOutside` callback.
  */
 function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily, fontStyle, lineHeight, textAnchor = 'start', verticalAnchor = 'start'}: VictoryChartLabelsProps) {
-    const typefaces = useChartDefaultTypeface();
+    const typefaces = useChartTypefaces();
     const processedLines = text.split('\n').reduce(
         (acc, line, index) => {
             const lineColor = color?.[index];
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
index f9aa0ebccc78..9ef0370ca409 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
@@ -1,7 +1,7 @@
 import {Circle, Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React, {Fragment} from 'react';
-import {getChartSkiaTypeface, useChartDefaultTypeface} from '@components/Charts/hooks';
+import {getChartSkiaTypeface, useChartTypefaces} from '@components/Charts/hooks';
 import type {LegendItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 
 type VictoryChartLegendProps = LegendItem;
@@ -23,7 +23,7 @@ type ProcessedEntry = {
  * Intended for use inside CartesianChart's `renderOutside` callback.
  */
 function VictoryChartLegend({x, y, entries, gutter, symbolSpacer}: VictoryChartLegendProps) {
-    const typefaces = useChartDefaultTypeface();
+    const typefaces = useChartTypefaces();
     const processedEntries = entries.reduce(
         (acc, {text, color, fontSize, fontWeight, fontFamily, fontStyle, symbolColor, symbolSize}) => {
             const typeface = getChartSkiaTypeface(typefaces, {fontFamily, fontStyle, fontWeight});
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
index d9c921e437bf..684a667d6d05 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {PolarChart} from 'victory-native';
-import {ChartDefaultTypefaceProvider} from '@components/Charts/hooks';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import {COLOR_KEY, LABEL_KEY, VALUE_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -21,7 +21,7 @@ function VictoryChartPolar() {
             valueKey={VALUE_KEY}
             colorKey={COLOR_KEY}
         >
-            <ChartDefaultTypefaceProvider>
+            <ChartFontsProvider>
                 {tnode.children.map((child) => (
                     <VictoryChartCategories
                         key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
@@ -40,7 +40,7 @@ function VictoryChartPolar() {
                         {...legendItem}
                     />
                 ))}
-            </ChartDefaultTypefaceProvider>
+            </ChartFontsProvider>
         </PolarChart>
     );
 }
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
index 057903ebb03a..45b051dd00e5 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
@@ -1,6 +1,6 @@
 import React, {createContext, useContext} from 'react';
 import type {TNode} from 'react-native-render-html';
-import {useChartDefaultTypeface} from '@components/Charts/hooks';
+import {useChartTypefaces} from '@components/Charts/hooks';
 import {CHART_TYPE, LABEL_KEY, X_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
 import processVictoryChartTree from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/processVictoryChartTree';
 import type {ChartType, ProcessNodeResult} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
@@ -32,7 +32,7 @@ const VictoryChartContext = createContext<VictoryChartContextValue | null>(null)
  * Returns null when the chart data is invalid (no data points, or mixed cartesian/polar content).
  */
 function VictoryChartProvider({tnode, children}: {tnode: TNode; children: React.ReactNode}) {
-    const typefaces = useChartDefaultTypeface();
+    const typefaces = useChartTypefaces();
     const {data, xKey, yKeys, xAxis, yAxis, domain, domainPadding, padding, isHorizontal, categories, labelItems, legendItems} = processVictoryChartTree(tnode, typefaces.EXP_NEUE, null);
     const {nodeStyles: chartContentStyles, parentNodeStyles: chartContainerStyles} = parseStyles(tnode);
 

From 7e162d891008cabaf2a4b6e5070f7bd841d9404b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:50:33 -0700
Subject: [PATCH 17/35] feat(charts): unify Bar/Line fonts via
 ChartFontsProvider

Expand paragraph font fallbacks and wrap bar/line charts with ChartFontsProvider
so axis labels use the shared cached fontMgr from context.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/BarChart/BarChartContent.tsx   | 11 ++++++++++-
 src/components/Charts/LineChart/LineChartContent.tsx | 11 ++++++++++-
 src/components/Charts/VictoryTheme.ts                |  2 +-
 src/components/Charts/hooks/index.ts                 |  4 +---
 4 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/components/Charts/BarChart/BarChartContent.tsx b/src/components/Charts/BarChart/BarChartContent.tsx
index ecfb90e8a1b6..6a038b1ccef7 100644
--- a/src/components/Charts/BarChart/BarChartContent.tsx
+++ b/src/components/Charts/BarChart/BarChartContent.tsx
@@ -11,6 +11,7 @@ import ChartXAxisLabels from '@components/Charts/components/ChartXAxisLabels';
 import ChartYAxisLabels from '@components/Charts/components/ChartYAxisLabels';
 import type {HitTestArgs} from '@components/Charts/hooks';
 import {
+    ChartFontsProvider,
     useChartFontManager,
     useChartInteractions,
     useChartLabelFormats,
@@ -44,7 +45,7 @@ type BarChartProps = CartesianChartProps & {
     useSingleColor?: boolean;
 };
 
-function BarChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', useSingleColor = false, onBarPress}: BarChartProps) {
+function BarChartContentBody({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', useSingleColor = false, onBarPress}: BarChartProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const fontMgr = useChartFontManager();
@@ -301,6 +302,14 @@ function BarChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left'
     );
 }
 
+function BarChartContent(props: BarChartProps) {
+    return (
+        <ChartFontsProvider>
+            <BarChartContentBody {...props} />
+        </ChartFontsProvider>
+    );
+}
+
 export default BarChartContent;
 export type {BarChartProps};
 export {BAR_INNER_PADDING};
diff --git a/src/components/Charts/LineChart/LineChartContent.tsx b/src/components/Charts/LineChart/LineChartContent.tsx
index f0129ac2ae4d..93b9e9a80051 100644
--- a/src/components/Charts/LineChart/LineChartContent.tsx
+++ b/src/components/Charts/LineChart/LineChartContent.tsx
@@ -13,6 +13,7 @@ import LeftFrameLine from '@components/Charts/components/LeftFrameLine';
 import ScatterPoints from '@components/Charts/components/ScatterPoints';
 import type {HitTestArgs} from '@components/Charts/hooks';
 import {
+    ChartFontsProvider,
     useChartFontManager,
     useChartInteractions,
     useChartLabelFormats,
@@ -47,7 +48,7 @@ type LineChartProps = CartesianChartProps & {
     onPointPress?: (dataPoint: ChartDataPoint, index: number) => void;
 };
 
-function LineChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', onPointPress}: LineChartProps) {
+function LineChartContentBody({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', onPointPress}: LineChartProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const fontMgr = useChartFontManager();
@@ -304,5 +305,13 @@ function LineChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left
     );
 }
 
+function LineChartContent(props: LineChartProps) {
+    return (
+        <ChartFontsProvider>
+            <LineChartContentBody {...props} />
+        </ChartFontsProvider>
+    );
+}
+
 export default LineChartContent;
 export type {LineChartProps};
diff --git a/src/components/Charts/VictoryTheme.ts b/src/components/Charts/VictoryTheme.ts
index 78c83379b0d0..8db0296cd17a 100644
--- a/src/components/Charts/VictoryTheme.ts
+++ b/src/components/Charts/VictoryTheme.ts
@@ -4,7 +4,7 @@
 import colors from '@styles/theme/colors';
 
 /** Font families used by all chart label components (Paragraph API multi-font fallback). */
-const CHART_FONT_FAMILIES = ['ExpensifyNeue', 'NotoSansSymbols', 'NotoSansSCMonths'];
+const CHART_FONT_FAMILIES = ['ExpensifyNeue', 'ExpensifyMono', 'ExpensifyNewKansas', 'NotoSansSymbols', 'NotoSansSCMonths'];
 
 /**
  * Expensify Chart Color Palette.
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 83685f385cd3..2710594037f3 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -2,11 +2,9 @@ export {default as useChartLabelLayout} from './useChartLabelLayout';
 export {default as useChartLabelMeasurements} from './useChartLabelMeasurements';
 export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
-export {default as useChartFontManager} from './useChartFontManager/useChartFontManager';
-export {default as ChartDefaultTypefaceProvider} from '@components/Charts/context/ChartDefaultTypefaceProvider';
+export {useChartFontManager} from '@components/Charts/context/ChartFontsContext';
 export {default as ChartFontsProvider} from '@components/Charts/context/ChartFontsProvider';
 export {default as getChartSkiaTypeface} from '@components/Charts/utils/getChartSkiaTypeface';
-export {useChartDefaultTypeface} from '@components/Charts/context/ChartDefaultTypefaceContext';
 export {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';

From e38912c74ee0675e129fa0c40275e8158b87b5c5 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:50:37 -0700
Subject: [PATCH 18/35] refactor(charts): remove legacy chart font loading

Delete ChartDefaultTypefaceProvider and per-hook Skia font loaders now
replaced by the shared chartFontsCache and ChartFontsProvider.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 ...ry-native-cartesian-skia-context-bridge.md | 54 +++++++++++++++++++
 .../context/ChartDefaultTypefaceContext.tsx   | 14 -----
 .../ChartDefaultTypefaceProvider.native.tsx   | 13 -----
 .../context/ChartDefaultTypefaceProvider.tsx  | 13 -----
 .../useChartFontManager.native.ts             | 17 ------
 .../useChartFontManager.ts                    | 18 -------
 .../hooks/useChartSkiaTypefaces.native.ts     | 21 --------
 .../Charts/hooks/useChartSkiaTypefaces.ts     | 21 --------
 8 files changed, 54 insertions(+), 117 deletions(-)
 create mode 100644 docs/victory-native-cartesian-skia-context-bridge.md
 delete mode 100644 src/components/Charts/context/ChartDefaultTypefaceContext.tsx
 delete mode 100644 src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
 delete mode 100644 src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
 delete mode 100644 src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
 delete mode 100644 src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
 delete mode 100644 src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
 delete mode 100644 src/components/Charts/hooks/useChartSkiaTypefaces.ts

diff --git a/docs/victory-native-cartesian-skia-context-bridge.md b/docs/victory-native-cartesian-skia-context-bridge.md
new file mode 100644
index 000000000000..f86baf3b4ef5
--- /dev/null
+++ b/docs/victory-native-cartesian-skia-context-bridge.md
@@ -0,0 +1,54 @@
+# Victory Native: Cartesian chart Skia context bridge
+
+## Problem
+
+Custom React components rendered inside `CartesianChart` (including the `renderOutside` callback) cannot read React context from ancestors above the chart—for example, a `ChartFontsProvider` wrapping the HTML Victory chart tree.
+
+This surfaces as runtime errors such as:
+
+```text
+useChartTypefaces must be used within ChartFontsProvider
+```
+
+The failure occurs in Skia overlay components (`VictoryChartLabel`, `VictoryChartLegend`) even when the provider is correctly placed on the main React tree (`BaseVictoryChartRenderer`).
+
+## Cause
+
+`victory-native` draws charts inside `@shopify/react-native-skia`’s `<Canvas>`, which uses a **separate React reconciler**. Context from the host app does not cross into that tree unless it is explicitly bridged.
+
+| Chart type        | Context bridge (`its-fine`) | Notes                                      |
+| ----------------- | --------------------------- | ------------------------------------------ |
+| `PolarChart`      | Yes                         | Added in [victory-native-xl#440](https://github.com/FormidableLabs/victory-native-xl/pull/440) |
+| `CartesianChart`  | No                          | `children` and `renderOutside` run inside `<Canvas>` without a bridge |
+
+`ChartWrapper` already exposes `wrapCanvasContent` for this purpose; `PolarChart` uses it, `CartesianChart` does not.
+
+## Impact on Expensify App
+
+Victory HTML charts load default typefaces via `ChartFontsProvider` and consume them with `useChartTypefaces()` in label/legend components. After moving font loading into that provider, cartesian overlays broke until we added **nested** `ChartFontsProvider` instances inside `renderOutside` (and polar children)—which reloads fonts and duplicates logic.
+
+## Proposed upstream solution
+
+**Repository:** [FormidableLabs/victory-native-xl](https://github.com/FormidableLabs/victory-native-xl) (npm package: `victory-native`)
+
+**Change:** Give `CartesianChart` the same `its-fine` pattern as `PolarChart`:
+
+1. Wrap the cartesian chart entry in `<FiberProvider>` (from `its-fine`, already a dependency).
+2. In the component that renders `ChartWrapper`, call `useContextBridge()` and pass  
+   `wrapCanvasContent={(content) => <Bridge>{content}</Bridge>}`  
+   (omit in headless mode, matching polar).
+
+All canvas content—including `children(renderArg)` and `renderOutside(renderArg)`—is built into `chartContent` and passed through `ChartWrapper`, so one bridge covers both.
+
+**Scope:** Small parity fix; no new public API. Aligns with the intent of [#433](https://github.com/FormidableLabs/victory-native-xl/issues/433) / [#440](https://github.com/FormidableLabs/victory-native-xl/pull/440).
+
+**Suggested PR title:** `Add its-fine context bridge to CartesianChart (parity with PolarChart)`
+
+## App workaround (until upstream ships)
+
+Until a `victory-native` release includes the bridge:
+
+- Keep a single `ChartFontsProvider` on `BaseVictoryChartRenderer` for `VictoryChartProvider` / tree parsing.
+- Add nested providers (or a local `its-fine` bridge) at Skia boundaries where context is still missing—today: `VictoryChartCartesian` `renderOutside` and `VictoryChartPolar` children.
+
+After upgrading to a fixed `victory-native` version, remove nested providers and rely on the outer provider only.
diff --git a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx b/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
deleted file mode 100644
index 6ffdb6ac6037..000000000000
--- a/src/components/Charts/context/ChartDefaultTypefaceContext.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import {createContext, useContext} from 'react';
-import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
-
-const ChartDefaultTypefaceContext = createContext<ChartDefaultTypeface | null>(null);
-
-function useChartDefaultTypeface(): ChartDefaultTypeface {
-    const context = useContext(ChartDefaultTypefaceContext);
-    if (!context) {
-        throw new Error('useChartDefaultTypeface must be used within ChartDefaultTypefaceProvider');
-    }
-    return context;
-}
-
-export {ChartDefaultTypefaceContext, useChartDefaultTypeface};
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
deleted file mode 100644
index a7fbfdc14df0..000000000000
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.native.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import useChartSkiaTypefaces from '@components/Charts/hooks/useChartSkiaTypefaces';
-import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
-
-function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
-    const typefaces = useChartSkiaTypefaces();
-
-    return <ChartDefaultTypefaceContext.Provider value={typefaces}>{children}</ChartDefaultTypefaceContext.Provider>;
-}
-
-ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
-
-export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx b/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
deleted file mode 100644
index a7fbfdc14df0..000000000000
--- a/src/components/Charts/context/ChartDefaultTypefaceProvider.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import useChartSkiaTypefaces from '@components/Charts/hooks/useChartSkiaTypefaces';
-import {ChartDefaultTypefaceContext} from './ChartDefaultTypefaceContext';
-
-function ChartDefaultTypefaceProvider({children}: {children: React.ReactNode}) {
-    const typefaces = useChartSkiaTypefaces();
-
-    return <ChartDefaultTypefaceContext.Provider value={typefaces}>{children}</ChartDefaultTypefaceContext.Provider>;
-}
-
-ChartDefaultTypefaceProvider.displayName = 'ChartDefaultTypefaceProvider';
-
-export default ChartDefaultTypefaceProvider;
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
deleted file mode 100644
index 5b3f293c2def..000000000000
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.native.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type {DataModule, SkTypefaceFontProvider} from '@shopify/react-native-skia';
-import {useFonts} from '@shopify/react-native-skia';
-
-function useChartFontManager(): SkTypefaceFontProvider | null {
-    return useFonts({
-        ExpensifyNeue: [
-            require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule,
-            require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule,
-            require('@assets/fonts/native/ExpensifyNeue-Italic.otf') as DataModule,
-            require('@assets/fonts/native/ExpensifyNeue-BoldItalic.otf') as DataModule,
-        ],
-        NotoSansSymbols: [require('@assets/fonts/NotoSans-Symbols.ttf') as DataModule],
-        NotoSansSCMonths: [require('@assets/fonts/NotoSansSC-Months.ttf') as DataModule],
-    });
-}
-
-export default useChartFontManager;
diff --git a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts b/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
deleted file mode 100644
index 3b11301f4646..000000000000
--- a/src/components/Charts/hooks/useChartFontManager/useChartFontManager.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
-import {useFonts} from '@shopify/react-native-skia';
-import chartWebFont from '@components/Charts/utils/chartWebFont';
-
-function useChartFontManager(): SkTypefaceFontProvider | null {
-    return useFonts({
-        ExpensifyNeue: [
-            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string),
-            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string),
-            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string),
-            chartWebFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string),
-        ],
-        NotoSansSymbols: [chartWebFont(require('@assets/fonts/NotoSans-Symbols.ttf') as string)],
-        NotoSansSCMonths: [chartWebFont(require('@assets/fonts/NotoSansSC-Months.ttf') as string)],
-    });
-}
-
-export default useChartFontManager;
diff --git a/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
deleted file mode 100644
index f74cbfb96189..000000000000
--- a/src/components/Charts/hooks/useChartSkiaTypefaces.native.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type {DataModule} from '@shopify/react-native-skia';
-import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
-
-function useChartSkiaTypefaces(): ChartDefaultTypeface {
-    return {
-        MONOSPACE: useTypeface(require('@assets/fonts/native/ExpensifyMono-Regular.otf') as DataModule),
-        MONOSPACE_BOLD: useTypeface(require('@assets/fonts/native/ExpensifyMono-Bold.otf') as DataModule),
-        MONOSPACE_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyMono-Italic.otf') as DataModule),
-        MONOSPACE_BOLD_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyMono-BoldItalic.otf') as DataModule),
-        EXP_NEUE: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Regular.otf') as DataModule),
-        EXP_NEUE_BOLD: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Bold.otf') as DataModule),
-        EXP_NEUE_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNeue-Italic.otf') as DataModule),
-        EXP_NEUE_BOLD_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNeue-BoldItalic.otf') as DataModule),
-        EXP_NEW_KANSAS_MEDIUM: useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-Medium.otf') as DataModule),
-        EXP_NEW_KANSAS_MEDIUM_ITALIC: useTypeface(require('@assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf') as DataModule),
-        CUSTOM_EMOJI_FONT: useTypeface(require('@assets/fonts/native/CustomEmojiNativeFont.ttf') as DataModule),
-    };
-}
-
-export default useChartSkiaTypefaces;
diff --git a/src/components/Charts/hooks/useChartSkiaTypefaces.ts b/src/components/Charts/hooks/useChartSkiaTypefaces.ts
deleted file mode 100644
index aa95fe01dac0..000000000000
--- a/src/components/Charts/hooks/useChartSkiaTypefaces.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {useTypeface} from '@shopify/react-native-skia';
-import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
-import chartWebFont from '@components/Charts/utils/chartWebFont';
-
-function useChartSkiaTypefaces(): ChartDefaultTypeface {
-    return {
-        MONOSPACE: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Regular.woff2') as string)),
-        MONOSPACE_BOLD: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Bold.woff2') as string)),
-        MONOSPACE_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-Italic.woff2') as string)),
-        MONOSPACE_BOLD_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyMono-BoldItalic.woff2') as string)),
-        EXP_NEUE: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Regular.woff2') as string)),
-        EXP_NEUE_BOLD: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Bold.woff2') as string)),
-        EXP_NEUE_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-Italic.woff2') as string)),
-        EXP_NEUE_BOLD_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNeue-BoldItalic.woff2') as string)),
-        EXP_NEW_KANSAS_MEDIUM: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-Medium.woff2') as string)),
-        EXP_NEW_KANSAS_MEDIUM_ITALIC: useTypeface(chartWebFont(require('@assets/fonts/web/ExpensifyNewKansas-MediumItalic.woff2') as string)),
-        CUSTOM_EMOJI_FONT: useTypeface(chartWebFont(require('@assets/fonts/web/CustomEmojiWebFont.ttf') as string)),
-    };
-}
-
-export default useChartSkiaTypefaces;

From bdeaf13d5f0b14cb44aff36cf0196ab15c642d39 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 10:52:08 -0700
Subject: [PATCH 19/35] chore: remove victory-native cartesian skia context
 bridge doc

This doc was added by mistake and is not needed in the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 ...ry-native-cartesian-skia-context-bridge.md | 54 -------------------
 1 file changed, 54 deletions(-)
 delete mode 100644 docs/victory-native-cartesian-skia-context-bridge.md

diff --git a/docs/victory-native-cartesian-skia-context-bridge.md b/docs/victory-native-cartesian-skia-context-bridge.md
deleted file mode 100644
index f86baf3b4ef5..000000000000
--- a/docs/victory-native-cartesian-skia-context-bridge.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# Victory Native: Cartesian chart Skia context bridge
-
-## Problem
-
-Custom React components rendered inside `CartesianChart` (including the `renderOutside` callback) cannot read React context from ancestors above the chart—for example, a `ChartFontsProvider` wrapping the HTML Victory chart tree.
-
-This surfaces as runtime errors such as:
-
-```text
-useChartTypefaces must be used within ChartFontsProvider
-```
-
-The failure occurs in Skia overlay components (`VictoryChartLabel`, `VictoryChartLegend`) even when the provider is correctly placed on the main React tree (`BaseVictoryChartRenderer`).
-
-## Cause
-
-`victory-native` draws charts inside `@shopify/react-native-skia`’s `<Canvas>`, which uses a **separate React reconciler**. Context from the host app does not cross into that tree unless it is explicitly bridged.
-
-| Chart type        | Context bridge (`its-fine`) | Notes                                      |
-| ----------------- | --------------------------- | ------------------------------------------ |
-| `PolarChart`      | Yes                         | Added in [victory-native-xl#440](https://github.com/FormidableLabs/victory-native-xl/pull/440) |
-| `CartesianChart`  | No                          | `children` and `renderOutside` run inside `<Canvas>` without a bridge |
-
-`ChartWrapper` already exposes `wrapCanvasContent` for this purpose; `PolarChart` uses it, `CartesianChart` does not.
-
-## Impact on Expensify App
-
-Victory HTML charts load default typefaces via `ChartFontsProvider` and consume them with `useChartTypefaces()` in label/legend components. After moving font loading into that provider, cartesian overlays broke until we added **nested** `ChartFontsProvider` instances inside `renderOutside` (and polar children)—which reloads fonts and duplicates logic.
-
-## Proposed upstream solution
-
-**Repository:** [FormidableLabs/victory-native-xl](https://github.com/FormidableLabs/victory-native-xl) (npm package: `victory-native`)
-
-**Change:** Give `CartesianChart` the same `its-fine` pattern as `PolarChart`:
-
-1. Wrap the cartesian chart entry in `<FiberProvider>` (from `its-fine`, already a dependency).
-2. In the component that renders `ChartWrapper`, call `useContextBridge()` and pass  
-   `wrapCanvasContent={(content) => <Bridge>{content}</Bridge>}`  
-   (omit in headless mode, matching polar).
-
-All canvas content—including `children(renderArg)` and `renderOutside(renderArg)`—is built into `chartContent` and passed through `ChartWrapper`, so one bridge covers both.
-
-**Scope:** Small parity fix; no new public API. Aligns with the intent of [#433](https://github.com/FormidableLabs/victory-native-xl/issues/433) / [#440](https://github.com/FormidableLabs/victory-native-xl/pull/440).
-
-**Suggested PR title:** `Add its-fine context bridge to CartesianChart (parity with PolarChart)`
-
-## App workaround (until upstream ships)
-
-Until a `victory-native` release includes the bridge:
-
-- Keep a single `ChartFontsProvider` on `BaseVictoryChartRenderer` for `VictoryChartProvider` / tree parsing.
-- Add nested providers (or a local `its-fine` bridge) at Skia boundaries where context is still missing—today: `VictoryChartCartesian` `renderOutside` and `VictoryChartPolar` children.
-
-After upgrading to a fixed `victory-native` version, remove nested providers and rely on the outer provider only.

From 7ce823bed9dcaf23b6339fd0b40fdbf2a96f44ec Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 11:04:44 -0700
Subject: [PATCH 20/35] refactor(charts): improve font load errors and drop
 useMemo

Log chart font failures in chartFontsCache and remove hook-level catch.
Drop useMemo from VictoryChartPie for React Compiler compliance.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/hooks/useChartFonts.ts      |  4 +---
 src/components/Charts/utils/chartFontsCache.ts    |  9 +++++++--
 .../components/VictoryChartPie.tsx                | 15 ++++++---------
 3 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/components/Charts/hooks/useChartFonts.ts b/src/components/Charts/hooks/useChartFonts.ts
index 365cc260206b..bcfeb445bcce 100644
--- a/src/components/Charts/hooks/useChartFonts.ts
+++ b/src/components/Charts/hooks/useChartFonts.ts
@@ -6,9 +6,7 @@ function useChartFonts(): ChartFontsValue {
     const fonts = useSyncExternalStore(subscribeToChartFonts, getChartFontsSnapshot, getChartFontsSnapshot);
 
     useEffect(() => {
-        loadChartFontsOnce().catch(() => {
-            // Chart consumers null-guard fontMgr and typefaces until load succeeds.
-        });
+        loadChartFontsOnce();
     }, []);
 
     return fonts;
diff --git a/src/components/Charts/utils/chartFontsCache.ts b/src/components/Charts/utils/chartFontsCache.ts
index 62874391ca85..4b9ac4a4dbbd 100644
--- a/src/components/Charts/utils/chartFontsCache.ts
+++ b/src/components/Charts/utils/chartFontsCache.ts
@@ -3,6 +3,7 @@ import {Skia} from '@shopify/react-native-skia';
 import {Image} from 'react-native';
 import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
 import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import Log from '@libs/Log';
 import {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
 import createChartFontsValue from './createChartFontsValue';
 
@@ -108,9 +109,13 @@ function loadChartFontsOnce(): Promise<ChartFontsValue> {
                 notifyChartFontLoadListeners();
                 return fonts;
             })
-            .catch((error) => {
+            .catch((error: unknown) => {
+                Log.hmmm('Chart fonts failed to load', {
+                    error: error instanceof Error ? error.message : String(error),
+                });
                 loadPromise = null;
-                throw error;
+                notifyChartFontLoadListeners();
+                return EMPTY_CHART_FONTS;
             });
     }
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 4d9d9612f3f0..9d1f1f800dc6 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -1,4 +1,4 @@
-import React, {useMemo} from 'react';
+import React from 'react';
 import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import {Pie} from 'victory-native';
@@ -22,14 +22,11 @@ function VictoryChartPie({tnode}: VictoryChartPieProps) {
     const size = radius ? radius * 2 : undefined;
     const labelRadius = tnode.attributes.labelradius !== undefined ? Number(parseAttribute(tnode.attributes.labelradius)) : undefined;
 
-    const pieLabelConfig = useMemo(() => {
-        const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
-        const labelItemTemplate = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
-        const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
-        const labels = parseAttribute<string[]>(tnode.attributes.labels);
-
-        return {labelItemTemplate, dataLabels, labels};
-    }, [data, renderEngine, tnode.attributes.labelcomponent, tnode.attributes.labels]);
+    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
+    const labelItemTemplate = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
+    const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
+    const labels = parseAttribute<string[]>(tnode.attributes.labels);
+    const pieLabelConfig = {labelItemTemplate, dataLabels, labels};
 
     return (
         <Pie.Chart

From ab7ab31af5543472d00a3e1a6983b17f224daf1b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 11:05:46 -0700
Subject: [PATCH 21/35] chore(charts): remove redundant component displayNames

Drop displayName assignments that only repeat the function name.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/ChartFontsProvider.native.tsx   | 4 ----
 src/components/Charts/context/ChartFontsProvider.tsx          | 4 ----
 .../VictoryChartRenderer/BaseVictoryChartRenderer.tsx         | 2 --
 .../VictoryChartRenderer/components/VictoryChartCartesian.tsx | 2 --
 .../VictoryChartRenderer/components/VictoryChartLabel.tsx     | 2 --
 .../VictoryChartRenderer/components/VictoryChartLegend.tsx    | 2 --
 .../VictoryChartRenderer/components/VictoryChartPie.tsx       | 2 --
 .../VictoryChartRenderer/components/VictoryChartPieLabel.tsx  | 2 --
 .../VictoryChartRenderer/components/VictoryChartPolar.tsx     | 2 --
 .../VictoryChartRenderer/context/VictoryChartContext.tsx      | 2 --
 10 files changed, 24 deletions(-)

diff --git a/src/components/Charts/context/ChartFontsProvider.native.tsx b/src/components/Charts/context/ChartFontsProvider.native.tsx
index 4f98b3223b4f..3db66295e728 100644
--- a/src/components/Charts/context/ChartFontsProvider.native.tsx
+++ b/src/components/Charts/context/ChartFontsProvider.native.tsx
@@ -14,8 +14,6 @@ function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
     return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
 }
 
-ChartFontsLoaderProvider.displayName = 'ChartFontsLoaderProvider';
-
 function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
     if (value) {
         return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
@@ -24,6 +22,4 @@ function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
     return <ChartFontsLoaderProvider>{children}</ChartFontsLoaderProvider>;
 }
 
-ChartFontsProvider.displayName = 'ChartFontsProvider';
-
 export default ChartFontsProvider;
diff --git a/src/components/Charts/context/ChartFontsProvider.tsx b/src/components/Charts/context/ChartFontsProvider.tsx
index 4f98b3223b4f..3db66295e728 100644
--- a/src/components/Charts/context/ChartFontsProvider.tsx
+++ b/src/components/Charts/context/ChartFontsProvider.tsx
@@ -14,8 +14,6 @@ function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
     return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
 }
 
-ChartFontsLoaderProvider.displayName = 'ChartFontsLoaderProvider';
-
 function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
     if (value) {
         return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
@@ -24,6 +22,4 @@ function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
     return <ChartFontsLoaderProvider>{children}</ChartFontsLoaderProvider>;
 }
 
-ChartFontsProvider.displayName = 'ChartFontsProvider';
-
 export default ChartFontsProvider;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
index e058d52f31b2..2ceb4d4ac69f 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/BaseVictoryChartRenderer.tsx
@@ -17,6 +17,4 @@ function BaseVictoryChartRenderer({tnode}: VictoryChartRendererProps) {
     );
 }
 
-BaseVictoryChartRenderer.displayName = 'BaseVictoryChartRenderer';
-
 export default BaseVictoryChartRenderer;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index e6ed3e7232a9..819b691d8407 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -59,6 +59,4 @@ function VictoryChartCartesian() {
     );
 }
 
-VictoryChartCartesian.displayName = 'VictoryChartCartesian';
-
 export default VictoryChartCartesian;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
index 0efc95c9e0af..702c63dbca90 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
@@ -70,6 +70,4 @@ function VictoryChartLabel({x, y, text, color, fontSize, fontWeight, fontFamily,
     });
 }
 
-VictoryChartLabel.displayName = 'VictoryChartLabel';
-
 export default VictoryChartLabel;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
index 9ef0370ca409..6588c6fc9d61 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
@@ -75,6 +75,4 @@ function VictoryChartLegend({x, y, entries, gutter, symbolSpacer}: VictoryChartL
     });
 }
 
-VictoryChartLegend.displayName = 'VictoryChartLegend';
-
 export default VictoryChartLegend;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 9d1f1f800dc6..26a9a798cb48 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -49,6 +49,4 @@ function VictoryChartPie({tnode}: VictoryChartPieProps) {
     );
 }
 
-VictoryChartPie.displayName = 'VictoryChartPie';
-
 export default VictoryChartPie;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
index 9b0e6178e474..ae0dbb3bf80f 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
@@ -39,6 +39,4 @@ function VictoryChartPieLabel({slice, labelItemTemplate, dataLabels, labels, lab
     return <VictoryChartLabel {...labelItem} />;
 }
 
-VictoryChartPieLabel.displayName = 'VictoryChartPieLabel';
-
 export default VictoryChartPieLabel;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
index 684a667d6d05..ece38b95932b 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
@@ -45,6 +45,4 @@ function VictoryChartPolar() {
     );
 }
 
-VictoryChartPolar.displayName = 'VictoryChartPolar';
-
 export default VictoryChartPolar;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
index 45b051dd00e5..332d34825f69 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext.tsx
@@ -75,8 +75,6 @@ function VictoryChartProvider({tnode, children}: {tnode: TNode; children: React.
     return <VictoryChartContext.Provider value={contextValue}>{children}</VictoryChartContext.Provider>;
 }
 
-VictoryChartProvider.displayName = 'VictoryChartProvider';
-
 function useVictoryChartContext(): VictoryChartContextValue {
     const context = useContext(VictoryChartContext);
     if (!context) {

From f8a020016257e97377ad8ea747abb9a2aa53f3c8 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 11:45:08 -0700
Subject: [PATCH 22/35] fix(charts): keep VictoryChartContext out of
 VictoryChartPie

Move chart context usage into VictoryChartPieLabel (prod API) so polar
pie charts work without prop-drilling label config through the slice tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../components/VictoryChartPie.tsx            | 19 +-------
 .../components/VictoryChartPieLabel.tsx       | 48 ++++++++++---------
 2 files changed, 27 insertions(+), 40 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 26a9a798cb48..00bfbd3dbe0e 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -1,12 +1,7 @@
 import React from 'react';
-import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import {Pie} from 'victory-native';
-import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
-import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
-import type {PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
-import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
 import VictoryChartPieLabel from './VictoryChartPieLabel';
 
 type VictoryChartPieProps = {tnode: TNode};
@@ -15,18 +10,9 @@ type VictoryChartPieProps = {tnode: TNode};
 const START_ANGLE = 270;
 
 function VictoryChartPie({tnode}: VictoryChartPieProps) {
-    const {data} = useVictoryChartContext();
-    const renderEngine = useAmbientTRenderEngine();
     const innerRadius = tnode.attributes.innerradius !== undefined ? Number(parseAttribute(tnode.attributes.innerradius)) : undefined;
     const radius = tnode.attributes.radius !== undefined ? Number(parseAttribute(tnode.attributes.radius)) : undefined;
     const size = radius ? radius * 2 : undefined;
-    const labelRadius = tnode.attributes.labelradius !== undefined ? Number(parseAttribute(tnode.attributes.labelradius)) : undefined;
-
-    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
-    const labelItemTemplate = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
-    const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
-    const labels = parseAttribute<string[]>(tnode.attributes.labels);
-    const pieLabelConfig = {labelItemTemplate, dataLabels, labels};
 
     return (
         <Pie.Chart
@@ -37,11 +23,8 @@ function VictoryChartPie({tnode}: VictoryChartPieProps) {
             {({slice}) => (
                 <Pie.Slice>
                     <VictoryChartPieLabel
+                        tnode={tnode}
                         slice={slice}
-                        labelItemTemplate={pieLabelConfig.labelItemTemplate}
-                        dataLabels={pieLabelConfig.dataLabels}
-                        labels={pieLabelConfig.labels}
-                        labelRadius={labelRadius}
                     />
                 </Pie.Slice>
             )}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
index ae0dbb3bf80f..4eda84bbfcf1 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
@@ -1,40 +1,44 @@
 import React from 'react';
+import {useAmbientTRenderEngine} from 'react-native-render-html';
+import type {TNode} from 'react-native-render-html';
 import type {PieSliceData} from 'victory-native';
-import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
+import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
+import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
+import type {PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
+import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
+import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
 import VictoryChartLabel from './VictoryChartLabel';
 
 type VictoryChartPieLabelProps = {
+    tnode: TNode;
     slice: PieSliceData;
-    labelItemTemplate?: LabelItem;
-    dataLabels: Array<string | number>;
-    labels?: string[];
-    labelRadius?: number;
 };
 
 const RADIAN = Math.PI / 180;
 
-/**
- * Renders a pie slice label inside the Skia canvas. Must not use React context hooks
- * because this runs in victory-native's pie slice render callback.
- */
-function VictoryChartPieLabel({slice, labelItemTemplate, dataLabels, labels, labelRadius}: VictoryChartPieLabelProps) {
-    if (!labelItemTemplate) {
+function VictoryChartPieLabel({tnode, slice}: VictoryChartPieLabelProps) {
+    const {data} = useVictoryChartContext();
+    const renderEngine = useAmbientTRenderEngine();
+    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
+    const labelItem = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
+
+    if (!labelItem) {
         return null;
     }
 
+    const dataLabels = Object.values(data).map((entry) => (entry as PolarChartData).label);
+    const labels = parseAttribute<string[]>(tnode.attributes.labels);
     const text = labels?.[dataLabels.indexOf(slice.label)] ?? slice.label;
-    const resolvedLabelRadius = labelRadius ?? slice.radius;
+
+    const labelRadius = tnode.attributes.labelradius !== undefined ? Number(parseAttribute(tnode.attributes.labelradius)) : slice.radius;
     const midAngle = (slice.startAngle + slice.endAngle) / 2;
-    const x = slice.center.x + resolvedLabelRadius * Math.cos(-midAngle * RADIAN);
-    const y = slice.center.y + resolvedLabelRadius * Math.sin(midAngle * RADIAN);
-
-    const labelItem: LabelItem = {
-        ...labelItemTemplate,
-        text,
-        x,
-        y,
-        textAnchor: 'middle',
-    };
+    const x = slice.center.x + labelRadius * Math.cos(-midAngle * RADIAN);
+    const y = slice.center.y + labelRadius * Math.sin(midAngle * RADIAN);
+
+    labelItem.text = text;
+    labelItem.x = x;
+    labelItem.y = y;
+    labelItem.textAnchor = 'middle';
 
     return <VictoryChartLabel {...labelItem} />;
 }

From e919a423e455e6b3f9c8ad8ed74fdf2e72914f44 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 11:46:09 -0700
Subject: [PATCH 23/35] chore(charts): drop redundant ChartFontsProvider in
 cartesian overlays

BaseVictoryChartRenderer already wraps charts with ChartFontsProvider;
renderOutside labels can use that context like pie slice callbacks do.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../components/VictoryChartCartesian.tsx      | 31 +++++++++----------
 .../components/VictoryChartCategories.tsx     |  2 --
 2 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 819b691d8407..8c663ee4f5b1 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,6 +1,5 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
-import {ChartFontsProvider} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -26,22 +25,20 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <ChartFontsProvider>
-                    <VictoryChartRenderArgsProvider value={renderArgs}>
-                        {labelItems.map((labelItem) => (
-                            <VictoryChartLabel
-                                key={`label-${labelItem.x}-${labelItem.y}`}
-                                {...labelItem}
-                            />
-                        ))}
-                        {legendItems.map((legendItem) => (
-                            <VictoryChartLegend
-                                key={`legend-${legendItem.x}-${legendItem.y}`}
-                                {...legendItem}
-                            />
-                        ))}
-                    </VictoryChartRenderArgsProvider>
-                </ChartFontsProvider>
+                <VictoryChartRenderArgsProvider value={renderArgs}>
+                    {labelItems.map((labelItem) => (
+                        <VictoryChartLabel
+                            key={`label-${labelItem.x}-${labelItem.y}`}
+                            {...labelItem}
+                        />
+                    ))}
+                    {legendItems.map((legendItem) => (
+                        <VictoryChartLegend
+                            key={`legend-${legendItem.x}-${legendItem.y}`}
+                            {...legendItem}
+                        />
+                    ))}
+                </VictoryChartRenderArgsProvider>
             )}
         >
             {(renderArgs) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCategories.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCategories.tsx
index 348230c9c9da..5fdc144c38a8 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCategories.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCategories.tsx
@@ -24,6 +24,4 @@ function VictoryChartCategories({tnode}: VictoryChartCategoriesProps) {
     return <CategoriesRenderer tnode={tnode} />;
 }
 
-VictoryChartCategories.displayName = 'VictoryChartCategories';
-
 export default VictoryChartCategories;

From 164b5fde6c04738cf61434ca487b8847d0cf6e9e Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 11:47:41 -0700
Subject: [PATCH 24/35] fix(charts): restore ChartFontsProvider in cartesian
 renderOutside

ChartFontsContext does not reach renderOutside from the base provider;
nested ChartFontsProvider is required for overlay labels and legends.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../components/VictoryChartCartesian.tsx      | 31 ++++++++++---------
 1 file changed, 17 insertions(+), 14 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 8c663ee4f5b1..819b691d8407 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -25,20 +26,22 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <VictoryChartRenderArgsProvider value={renderArgs}>
-                    {labelItems.map((labelItem) => (
-                        <VictoryChartLabel
-                            key={`label-${labelItem.x}-${labelItem.y}`}
-                            {...labelItem}
-                        />
-                    ))}
-                    {legendItems.map((legendItem) => (
-                        <VictoryChartLegend
-                            key={`legend-${legendItem.x}-${legendItem.y}`}
-                            {...legendItem}
-                        />
-                    ))}
-                </VictoryChartRenderArgsProvider>
+                <ChartFontsProvider>
+                    <VictoryChartRenderArgsProvider value={renderArgs}>
+                        {labelItems.map((labelItem) => (
+                            <VictoryChartLabel
+                                key={`label-${labelItem.x}-${labelItem.y}`}
+                                {...labelItem}
+                            />
+                        ))}
+                        {legendItems.map((legendItem) => (
+                            <VictoryChartLegend
+                                key={`legend-${legendItem.x}-${legendItem.y}`}
+                                {...legendItem}
+                            />
+                        ))}
+                    </VictoryChartRenderArgsProvider>
+                </ChartFontsProvider>
             )}
         >
             {(renderArgs) => (

From e18dd95cfb6f4418b0cee52771d1b935515f2178 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:04:33 -0700
Subject: [PATCH 25/35] fix(charts): resolve bold string weight in
 getChartSkiaTypeface

Treat fontWeight 'bold' like 700, add unit tests for typeface resolution,
and iterate chart keys without casting SYSTEM entries.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/utils/getChartSkiaTypeface.ts      | 17 +++++---
 tests/unit/getChartSkiaTypefaceTest.ts        | 43 +++++++++++++++++++
 2 files changed, 55 insertions(+), 5 deletions(-)
 create mode 100644 tests/unit/getChartSkiaTypefaceTest.ts

diff --git a/src/components/Charts/utils/getChartSkiaTypeface.ts b/src/components/Charts/utils/getChartSkiaTypeface.ts
index 4b62a0c34a2c..288b93468e72 100644
--- a/src/components/Charts/utils/getChartSkiaTypeface.ts
+++ b/src/components/Charts/utils/getChartSkiaTypeface.ts
@@ -12,7 +12,11 @@ function normalizeFontStyle(fontStyle: string | undefined): ChartLabelFontStyle
 }
 
 function normalizeFontWeight(fontWeight: string | number | undefined): ChartLabelFontWeight {
-    return Number(fontWeight) === 700 ? 'bold' : 'normal';
+    if (fontWeight === 'bold' || Number(fontWeight) === 700) {
+        return 'bold';
+    }
+
+    return 'normal';
 }
 
 function fontWeightMatches(definitionWeight: TextStyle['fontWeight'], labelWeight: ChartLabelFontWeight): boolean {
@@ -32,11 +36,14 @@ function getChartSkiaTypefaceKey(fontFamily: string | undefined, fontStyle: Char
         return fontStyle === 'italic' ? 'EXP_NEW_KANSAS_MEDIUM_ITALIC' : 'EXP_NEW_KANSAS_MEDIUM';
     }
 
-    const matchingEntry = (Object.entries(singleFontFamily) as Array<[ChartSkiaTypefaceKey, (typeof singleFontFamily)[ChartSkiaTypefaceKey]]>).find(
-        ([, definition]) => definition.fontFamily === resolvedFontFamily && normalizeFontStyle(definition.fontStyle) === fontStyle && fontWeightMatches(definition.fontWeight, fontWeight),
-    );
+    const matchingKey = (Object.keys(singleFontFamily) as Array<keyof typeof singleFontFamily>)
+        .filter((key): key is ChartSkiaTypefaceKey => key !== 'SYSTEM')
+        .find((key) => {
+            const definition = singleFontFamily[key];
+            return definition.fontFamily === resolvedFontFamily && normalizeFontStyle(definition.fontStyle) === fontStyle && fontWeightMatches(definition.fontWeight, fontWeight);
+        });
 
-    return matchingEntry?.[0] ?? 'EXP_NEUE';
+    return matchingKey ?? 'EXP_NEUE';
 }
 
 function getChartSkiaTypeface(
diff --git a/tests/unit/getChartSkiaTypefaceTest.ts b/tests/unit/getChartSkiaTypefaceTest.ts
new file mode 100644
index 000000000000..18c4877b79a0
--- /dev/null
+++ b/tests/unit/getChartSkiaTypefaceTest.ts
@@ -0,0 +1,43 @@
+import type {SkTypeface} from '@shopify/react-native-skia';
+import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
+import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
+
+const CHART_SKIA_TYPEFACE_KEYS = Object.keys(singleFontFamily).filter((key): key is ChartSkiaTypefaceKey => key !== 'SYSTEM');
+
+function makeTypefaces(): ChartDefaultTypeface {
+    return Object.fromEntries(CHART_SKIA_TYPEFACE_KEYS.map((key) => [key, {id: key} as unknown as SkTypeface])) as ChartDefaultTypeface;
+}
+
+describe('getChartSkiaTypeface', () => {
+    const typefaces = makeTypefaces();
+
+    it('should resolve numeric bold weight to the bold typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontWeight: 700});
+        expect(typeface).toBe(typefaces.EXP_NEUE_BOLD);
+    });
+
+    it('should resolve string bold weight to the bold typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontWeight: 'bold'});
+        expect(typeface).toBe(typefaces.EXP_NEUE_BOLD);
+    });
+
+    it('should resolve normal weight to the regular typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontWeight: 400});
+        expect(typeface).toBe(typefaces.EXP_NEUE);
+    });
+
+    it('should resolve Expensify New Kansas by font family', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontFamily: singleFontFamily.EXP_NEW_KANSAS_MEDIUM.fontFamily});
+        expect(typeface).toBe(typefaces.EXP_NEW_KANSAS_MEDIUM);
+    });
+
+    it('should resolve italic Expensify Neue bold to the bold italic typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {
+            fontFamily: singleFontFamily.EXP_NEUE.fontFamily,
+            fontStyle: 'italic',
+            fontWeight: 'bold',
+        });
+        expect(typeface).toBe(typefaces.EXP_NEUE_BOLD_ITALIC);
+    });
+});

From 0949e9a346fb28edd46ddf743bb3c01578cbaa36 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:15:39 -0700
Subject: [PATCH 26/35] refactor(charts): dedupe font loads via shared
 constants

Build fontMgr from decoded typefaces plus Noto-only assets instead of
loading Neue, Mono, and Kansas twice. Share CHART_FONT_FAMILY_NAMES with
VictoryTheme and derive the empty cache typeface map from assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/VictoryTheme.ts         |  6 +--
 .../Charts/utils/chartFontAssets.native.ts    | 13 +++---
 .../Charts/utils/chartFontAssets.ts           | 13 +++---
 .../Charts/utils/chartFontConstants.ts        | 16 +++++++
 .../Charts/utils/chartFontsCache.ts           | 44 ++++++++++---------
 .../Charts/utils/createChartFontsValue.ts     | 18 --------
 .../components/Charts/VictoryTheme.test.ts    |  3 +-
 7 files changed, 53 insertions(+), 60 deletions(-)
 create mode 100644 src/components/Charts/utils/chartFontConstants.ts
 delete mode 100644 src/components/Charts/utils/createChartFontsValue.ts

diff --git a/src/components/Charts/VictoryTheme.ts b/src/components/Charts/VictoryTheme.ts
index 8db0296cd17a..a0bcfe21cf0a 100644
--- a/src/components/Charts/VictoryTheme.ts
+++ b/src/components/Charts/VictoryTheme.ts
@@ -2,9 +2,7 @@
  * Centralized styles and layout constants for the chart components.
  */
 import colors from '@styles/theme/colors';
-
-/** Font families used by all chart label components (Paragraph API multi-font fallback). */
-const CHART_FONT_FAMILIES = ['ExpensifyNeue', 'ExpensifyMono', 'ExpensifyNewKansas', 'NotoSansSymbols', 'NotoSansSCMonths'];
+import {CHART_FONT_FAMILY_NAMES} from './utils/chartFontConstants';
 
 /**
  * Expensify Chart Color Palette.
@@ -54,7 +52,7 @@ const VictoryTheme = {
         default: getChartColor(DEFAULT_CHART_COLOR_INDEX),
         getColor: getChartColor,
     },
-    fontFamilies: CHART_FONT_FAMILIES,
+    fontFamilies: [...CHART_FONT_FAMILY_NAMES],
     axis: {
         /** Number of Y-axis ticks (including zero) */
         tickCount: 5,
diff --git a/src/components/Charts/utils/chartFontAssets.native.ts b/src/components/Charts/utils/chartFontAssets.native.ts
index ba261225a1b4..cfb0b204cebd 100644
--- a/src/components/Charts/utils/chartFontAssets.native.ts
+++ b/src/components/Charts/utils/chartFontAssets.native.ts
@@ -29,12 +29,9 @@ const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
     CUSTOM_EMOJI_FONT,
 };
 
-const CHART_FONT_MANAGER_FAMILIES: Record<string, DataModule[]> = {
-    ExpensifyNeue: [EXPENSIFY_NEUE_REGULAR, EXPENSIFY_NEUE_BOLD, EXPENSIFY_NEUE_ITALIC, EXPENSIFY_NEUE_BOLD_ITALIC],
-    ExpensifyMono: [EXPENSIFY_MONO_REGULAR, EXPENSIFY_MONO_BOLD, EXPENSIFY_MONO_ITALIC, EXPENSIFY_MONO_BOLD_ITALIC],
-    ExpensifyNewKansas: [EXPENSIFY_NEW_KANSAS_MEDIUM, EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC],
-    NotoSansSymbols: [SANS_SYMBOLS_FONT],
-    NotoSansSCMonths: [SANS_SC_MONTHS_FONT],
-};
+const CHART_FONT_MGR_NOTO_ASSETS = {
+    NotoSansSymbols: SANS_SYMBOLS_FONT,
+    NotoSansSCMonths: SANS_SC_MONTHS_FONT,
+} as const;
 
-export {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS};
+export {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontAssets.ts b/src/components/Charts/utils/chartFontAssets.ts
index 26be32f9bbd9..1bd1654289d3 100644
--- a/src/components/Charts/utils/chartFontAssets.ts
+++ b/src/components/Charts/utils/chartFontAssets.ts
@@ -30,12 +30,9 @@ const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
     CUSTOM_EMOJI_FONT,
 };
 
-const CHART_FONT_MANAGER_FAMILIES: Record<string, DataModule[]> = {
-    ExpensifyNeue: [EXPENSIFY_NEUE_REGULAR, EXPENSIFY_NEUE_BOLD, EXPENSIFY_NEUE_ITALIC, EXPENSIFY_NEUE_BOLD_ITALIC],
-    ExpensifyMono: [EXPENSIFY_MONO_REGULAR, EXPENSIFY_MONO_BOLD, EXPENSIFY_MONO_ITALIC, EXPENSIFY_MONO_BOLD_ITALIC],
-    ExpensifyNewKansas: [EXPENSIFY_NEW_KANSAS_MEDIUM, EXPENSIFY_NEW_KANSAS_MEDIUM_ITALIC],
-    NotoSansSymbols: [SANS_SYMBOLS_FONT],
-    NotoSansSCMonths: [SANS_SC_MONTHS_FONT],
-};
+const CHART_FONT_MGR_NOTO_ASSETS = {
+    NotoSansSymbols: SANS_SYMBOLS_FONT,
+    NotoSansSCMonths: SANS_SC_MONTHS_FONT,
+} as const;
 
-export {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS};
+export {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontConstants.ts b/src/components/Charts/utils/chartFontConstants.ts
new file mode 100644
index 000000000000..3d98703a116e
--- /dev/null
+++ b/src/components/Charts/utils/chartFontConstants.ts
@@ -0,0 +1,16 @@
+import type {ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+
+/** Font families registered on Skia fontMgr and used by Paragraph API multi-font fallback. */
+const CHART_FONT_FAMILY_NAMES = ['ExpensifyNeue', 'ExpensifyMono', 'ExpensifyNewKansas', 'NotoSansSymbols', 'NotoSansSCMonths'] as const;
+
+type ChartFontFamilyName = (typeof CHART_FONT_FAMILY_NAMES)[number];
+
+/** Maps fontMgr family names to already-loaded chart typefaces (Noto families load separately). */
+const CHART_FONT_MGR_FROM_TYPEFACES: Record<Exclude<ChartFontFamilyName, 'NotoSansSymbols' | 'NotoSansSCMonths'>, ChartSkiaTypefaceKey[]> = {
+    ExpensifyNeue: ['EXP_NEUE', 'EXP_NEUE_BOLD', 'EXP_NEUE_ITALIC', 'EXP_NEUE_BOLD_ITALIC'],
+    ExpensifyMono: ['MONOSPACE', 'MONOSPACE_BOLD', 'MONOSPACE_ITALIC', 'MONOSPACE_BOLD_ITALIC'],
+    ExpensifyNewKansas: ['EXP_NEW_KANSAS_MEDIUM', 'EXP_NEW_KANSAS_MEDIUM_ITALIC'],
+};
+
+export {CHART_FONT_FAMILY_NAMES, CHART_FONT_MGR_FROM_TYPEFACES};
+export type {ChartFontFamilyName};
diff --git a/src/components/Charts/utils/chartFontsCache.ts b/src/components/Charts/utils/chartFontsCache.ts
index 4b9ac4a4dbbd..cb530d49abe1 100644
--- a/src/components/Charts/utils/chartFontsCache.ts
+++ b/src/components/Charts/utils/chartFontsCache.ts
@@ -4,23 +4,11 @@ import {Image} from 'react-native';
 import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
 import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
 import Log from '@libs/Log';
-import {CHART_FONT_MANAGER_FAMILIES, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
-import createChartFontsValue from './createChartFontsValue';
+import {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
+import {CHART_FONT_MGR_FROM_TYPEFACES} from './chartFontConstants';
 
 const EMPTY_CHART_FONTS: ChartFontsValue = {
-    typefaces: {
-        MONOSPACE: null,
-        MONOSPACE_BOLD: null,
-        MONOSPACE_ITALIC: null,
-        MONOSPACE_BOLD_ITALIC: null,
-        EXP_NEUE: null,
-        EXP_NEUE_BOLD: null,
-        EXP_NEUE_ITALIC: null,
-        EXP_NEUE_BOLD_ITALIC: null,
-        EXP_NEW_KANSAS_MEDIUM: null,
-        EXP_NEW_KANSAS_MEDIUM_ITALIC: null,
-        CUSTOM_EMOJI_FONT: null,
-    },
+    typefaces: Object.fromEntries((Object.keys(CHART_SKIA_TYPEFACE_ASSETS) as ChartSkiaTypefaceKey[]).map((key) => [key, null])) as ChartDefaultTypeface,
     fontMgr: null,
 };
 
@@ -65,18 +53,32 @@ function loadChartSkiaTypefaces(): Promise<ChartDefaultTypeface> {
     ).then((entries) => Object.fromEntries(entries) as ChartDefaultTypeface);
 }
 
-function loadFontManagerFamilies(): Promise<Record<string, SkTypeface[]>> {
+function buildChartFontsValue(typefaces: ChartDefaultTypeface): Promise<ChartFontsValue> {
+    const fontMgr = Skia.TypefaceFontProvider.Make();
+
+    for (const [familyName, typefaceKeys] of Object.entries(CHART_FONT_MGR_FROM_TYPEFACES)) {
+        for (const typefaceKey of typefaceKeys) {
+            const typeface = typefaces[typefaceKey];
+
+            if (typeface) {
+                fontMgr.registerFont(typeface, familyName);
+            }
+        }
+    }
+
     return Promise.all(
-        Object.entries(CHART_FONT_MANAGER_FAMILIES).map(async ([familyName, familyAssets]) => {
-            const familyTypefaces = await Promise.all(familyAssets.map((asset) => loadTypefaceFromAsset(asset)));
+        Object.entries(CHART_FONT_MGR_NOTO_ASSETS).map(async ([familyName, asset]) => {
+            const typeface = await loadTypefaceFromAsset(asset);
 
-            return [familyName, familyTypefaces.filter((typeface): typeface is SkTypeface => typeface !== null)] as const;
+            if (typeface) {
+                fontMgr.registerFont(typeface, familyName);
+            }
         }),
-    ).then((entries) => Object.fromEntries(entries));
+    ).then(() => ({typefaces, fontMgr}));
 }
 
 function loadChartFonts(): Promise<ChartFontsValue> {
-    return Promise.all([loadChartSkiaTypefaces(), loadFontManagerFamilies()]).then(([typefaces, fontManagerFamilies]) => createChartFontsValue(typefaces, fontManagerFamilies));
+    return loadChartSkiaTypefaces().then(buildChartFontsValue);
 }
 
 function subscribeToChartFonts(listener: () => void): () => void {
diff --git a/src/components/Charts/utils/createChartFontsValue.ts b/src/components/Charts/utils/createChartFontsValue.ts
deleted file mode 100644
index 5b5154f82030..000000000000
--- a/src/components/Charts/utils/createChartFontsValue.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type {SkTypeface} from '@shopify/react-native-skia';
-import {Skia} from '@shopify/react-native-skia';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
-import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
-
-function createChartFontsValue(typefaces: ChartDefaultTypeface, fontManagerFamilies: Record<string, SkTypeface[]>): ChartFontsValue {
-    const fontMgr = Skia.TypefaceFontProvider.Make();
-
-    for (const [familyName, familyTypefaces] of Object.entries(fontManagerFamilies)) {
-        for (const typeface of familyTypefaces) {
-            fontMgr.registerFont(typeface, familyName);
-        }
-    }
-
-    return {typefaces, fontMgr};
-}
-
-export default createChartFontsValue;
diff --git a/tests/unit/components/Charts/VictoryTheme.test.ts b/tests/unit/components/Charts/VictoryTheme.test.ts
index 44f821ce0a26..7960ec115f6a 100644
--- a/tests/unit/components/Charts/VictoryTheme.test.ts
+++ b/tests/unit/components/Charts/VictoryTheme.test.ts
@@ -1,4 +1,5 @@
 import type VictoryThemeType from '@components/Charts/VictoryTheme';
+import {CHART_FONT_FAMILY_NAMES} from '@components/Charts/utils/chartFontConstants';
 import colors from '@styles/theme/colors';
 
 /**
@@ -108,7 +109,7 @@ describe('VictoryTheme', () => {
     describe('static configuration values', () => {
         it('exposes the expected font families in order', () => {
             const VictoryTheme = loadVictoryTheme();
-            expect(VictoryTheme.fontFamilies).toEqual(['ExpensifyNeue', 'NotoSansSymbols', 'NotoSansSCMonths']);
+            expect(VictoryTheme.fontFamilies).toEqual([...CHART_FONT_FAMILY_NAMES]);
         });
 
         it('exposes axis values', () => {

From 7a0a7f904c73962ae5070a62b65788082cb176f6 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:15:43 -0700
Subject: [PATCH 27/35] refactor(charts): centralize chart font weight
 normalization

Share normalizeChartFontWeight across parsers and getChartSkiaTypeface so
string 'bold' and numeric semibold weights resolve consistently.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/utils/getChartSkiaTypeface.ts       | 14 ++++----------
 .../Charts/utils/normalizeChartFontWeight.ts   | 18 ++++++++++++++++++
 .../parsers/victoryLabelParser.ts              |  3 ++-
 .../parsers/victoryLegendParser.ts             |  3 ++-
 tests/unit/getChartSkiaTypefaceTest.ts         | 10 ++++++++++
 5 files changed, 36 insertions(+), 12 deletions(-)
 create mode 100644 src/components/Charts/utils/normalizeChartFontWeight.ts

diff --git a/src/components/Charts/utils/getChartSkiaTypeface.ts b/src/components/Charts/utils/getChartSkiaTypeface.ts
index 288b93468e72..c1f0bd1521ee 100644
--- a/src/components/Charts/utils/getChartSkiaTypeface.ts
+++ b/src/components/Charts/utils/getChartSkiaTypeface.ts
@@ -3,22 +3,15 @@ import type {TextStyle} from 'react-native';
 import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
 // eslint-disable-next-line no-restricted-imports
 import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
+import type {ChartLabelFontWeight} from './normalizeChartFontWeight';
+import normalizeChartFontWeight from './normalizeChartFontWeight';
 
 type ChartLabelFontStyle = 'normal' | 'italic';
-type ChartLabelFontWeight = 'normal' | 'bold';
 
 function normalizeFontStyle(fontStyle: string | undefined): ChartLabelFontStyle {
     return fontStyle === 'italic' ? 'italic' : 'normal';
 }
 
-function normalizeFontWeight(fontWeight: string | number | undefined): ChartLabelFontWeight {
-    if (fontWeight === 'bold' || Number(fontWeight) === 700) {
-        return 'bold';
-    }
-
-    return 'normal';
-}
-
 function fontWeightMatches(definitionWeight: TextStyle['fontWeight'], labelWeight: ChartLabelFontWeight): boolean {
     const numericWeight = Number(definitionWeight);
 
@@ -32,6 +25,7 @@ function fontWeightMatches(definitionWeight: TextStyle['fontWeight'], labelWeigh
 function getChartSkiaTypefaceKey(fontFamily: string | undefined, fontStyle: ChartLabelFontStyle, fontWeight: ChartLabelFontWeight): ChartSkiaTypefaceKey {
     const resolvedFontFamily = fontFamily ?? singleFontFamily.EXP_NEUE.fontFamily;
 
+    // Kansas has no bold variant; weight is ignored and Medium is always used.
     if (resolvedFontFamily === singleFontFamily.EXP_NEW_KANSAS_MEDIUM.fontFamily) {
         return fontStyle === 'italic' ? 'EXP_NEW_KANSAS_MEDIUM_ITALIC' : 'EXP_NEW_KANSAS_MEDIUM';
     }
@@ -58,7 +52,7 @@ function getChartSkiaTypeface(
         fontWeight?: string | number;
     },
 ): SkTypeface | null {
-    const typefaceKey = getChartSkiaTypefaceKey(fontFamily, normalizeFontStyle(fontStyle), normalizeFontWeight(fontWeight));
+    const typefaceKey = getChartSkiaTypefaceKey(fontFamily, normalizeFontStyle(fontStyle), normalizeChartFontWeight(fontWeight));
     return typefaces[typefaceKey];
 }
 
diff --git a/src/components/Charts/utils/normalizeChartFontWeight.ts b/src/components/Charts/utils/normalizeChartFontWeight.ts
new file mode 100644
index 000000000000..f2154fe10a66
--- /dev/null
+++ b/src/components/Charts/utils/normalizeChartFontWeight.ts
@@ -0,0 +1,18 @@
+type ChartLabelFontWeight = 'normal' | 'bold';
+
+function normalizeChartFontWeight(fontWeight: string | number | undefined): ChartLabelFontWeight {
+    if (fontWeight === 'bold') {
+        return 'bold';
+    }
+
+    const numericWeight = Number(fontWeight);
+
+    if (!Number.isNaN(numericWeight) && numericWeight >= 600) {
+        return 'bold';
+    }
+
+    return 'normal';
+}
+
+export default normalizeChartFontWeight;
+export type {ChartLabelFontWeight};
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
index e6e4f9649d79..78c4b7c61845 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser.ts
@@ -1,4 +1,5 @@
 import type {TNode} from 'react-native-render-html';
+import normalizeChartFontWeight from '@components/Charts/utils/normalizeChartFontWeight';
 import type {LabelItem, PartialProcessNodeResult, RawLabelStyle, TextAnchor} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
 
@@ -39,7 +40,7 @@ function parseVictoryLabelNode(tnode: TNode): PartialProcessNodeResult {
             if (textStyle.fontWeight) {
                 labelItem.fontWeight = {
                     ...labelItem.fontWeight,
-                    [index]: Number(textStyle.fontWeight) === 700 ? 'bold' : 'normal',
+                    [index]: normalizeChartFontWeight(textStyle.fontWeight),
                 };
             }
             if (textStyle.fontFamily) {
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
index f7cb5bb0cd26..6b848196008c 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLegendParser.ts
@@ -1,4 +1,5 @@
 import type {TNode} from 'react-native-render-html';
+import normalizeChartFontWeight from '@components/Charts/utils/normalizeChartFontWeight';
 import type {LegendItem, LegendItemEntry, PartialProcessNodeResult, RawLegendData, RawLegendStyle} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
 
@@ -13,7 +14,7 @@ function parseVictoryLegendNode(tnode: TNode): PartialProcessNodeResult {
     const style = parseAttribute<RawLegendStyle>(tnode.attributes.style);
     const color = style?.labels?.fill;
     const fontSize = style?.labels?.fontSize !== undefined ? Number(style.labels.fontSize) : undefined;
-    const fontWeight = Number(style?.labels?.fontWeight) === 700 ? 'bold' : undefined;
+    const fontWeight = style?.labels?.fontWeight !== undefined ? normalizeChartFontWeight(style.labels.fontWeight) : undefined;
     const fontFamily = style?.labels?.fontFamily;
     const fontStyle = style?.labels?.fontStyle;
     const entries: LegendItemEntry[] = (parseAttribute<RawLegendData[]>(tnode.attributes.data) ?? []).map((entry) => {
diff --git a/tests/unit/getChartSkiaTypefaceTest.ts b/tests/unit/getChartSkiaTypefaceTest.ts
index 18c4877b79a0..9abdcd35843f 100644
--- a/tests/unit/getChartSkiaTypefaceTest.ts
+++ b/tests/unit/getChartSkiaTypefaceTest.ts
@@ -27,6 +27,16 @@ describe('getChartSkiaTypeface', () => {
         expect(typeface).toBe(typefaces.EXP_NEUE);
     });
 
+    it('should resolve semibold numeric weight to the bold typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontWeight: 600});
+        expect(typeface).toBe(typefaces.EXP_NEUE_BOLD);
+    });
+
+    it('should resolve medium numeric weight to the regular typeface', () => {
+        const typeface = getChartSkiaTypeface(typefaces, {fontWeight: 500});
+        expect(typeface).toBe(typefaces.EXP_NEUE);
+    });
+
     it('should resolve Expensify New Kansas by font family', () => {
         const typeface = getChartSkiaTypeface(typefaces, {fontFamily: singleFontFamily.EXP_NEW_KANSAS_MEDIUM.fontFamily});
         expect(typeface).toBe(typefaces.EXP_NEW_KANSAS_MEDIUM);

From fad7fc07cdb2cbabd9ecad617537a6c23e62023e Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:15:46 -0700
Subject: [PATCH 28/35] refactor(charts): add ChartFontsBoundary for Skia
 overlays

Re-establish font context in cartesian renderOutside and polar subtrees
where React context does not cross the Skia boundary.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/context/ChartFontsBoundary.tsx        | 16 ++++++++++++++++
 src/components/Charts/hooks/index.ts             |  2 +-
 .../components/VictoryChartCartesian.tsx         |  6 +++---
 .../components/VictoryChartPolar.tsx             |  6 +++---
 4 files changed, 23 insertions(+), 7 deletions(-)
 create mode 100644 src/components/Charts/context/ChartFontsBoundary.tsx

diff --git a/src/components/Charts/context/ChartFontsBoundary.tsx b/src/components/Charts/context/ChartFontsBoundary.tsx
new file mode 100644
index 000000000000..d98f346ffc24
--- /dev/null
+++ b/src/components/Charts/context/ChartFontsBoundary.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import ChartFontsProvider from './ChartFontsProvider';
+
+type ChartFontsBoundaryProps = {
+    children: React.ReactNode;
+};
+
+/**
+ * Re-mounts chart font context inside Skia overlay subtrees (e.g. cartesian renderOutside,
+ * polar children) where React context does not propagate across the Skia boundary.
+ */
+function ChartFontsBoundary({children}: ChartFontsBoundaryProps) {
+    return <ChartFontsProvider>{children}</ChartFontsProvider>;
+}
+
+export default ChartFontsBoundary;
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 2710594037f3..4362541d8eae 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,7 +4,7 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {useChartFontManager} from '@components/Charts/context/ChartFontsContext';
 export {default as ChartFontsProvider} from '@components/Charts/context/ChartFontsProvider';
-export {default as getChartSkiaTypeface} from '@components/Charts/utils/getChartSkiaTypeface';
+export {default as ChartFontsBoundary} from '@components/Charts/context/ChartFontsBoundary';
 export {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 819b691d8407..4ab6a47ce991 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
-import {ChartFontsProvider} from '@components/Charts/hooks';
+import {ChartFontsBoundary} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -26,7 +26,7 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <ChartFontsProvider>
+                <ChartFontsBoundary>
                     <VictoryChartRenderArgsProvider value={renderArgs}>
                         {labelItems.map((labelItem) => (
                             <VictoryChartLabel
@@ -41,7 +41,7 @@ function VictoryChartCartesian() {
                             />
                         ))}
                     </VictoryChartRenderArgsProvider>
-                </ChartFontsProvider>
+                </ChartFontsBoundary>
             )}
         >
             {(renderArgs) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
index ece38b95932b..5a1a8b068999 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {PolarChart} from 'victory-native';
-import {ChartFontsProvider} from '@components/Charts/hooks';
+import {ChartFontsBoundary} from '@components/Charts/hooks';
 import {COLOR_KEY, LABEL_KEY, VALUE_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -21,7 +21,7 @@ function VictoryChartPolar() {
             valueKey={VALUE_KEY}
             colorKey={COLOR_KEY}
         >
-            <ChartFontsProvider>
+            <ChartFontsBoundary>
                 {tnode.children.map((child) => (
                     <VictoryChartCategories
                         key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
@@ -40,7 +40,7 @@ function VictoryChartPolar() {
                         {...legendItem}
                     />
                 ))}
-            </ChartFontsProvider>
+            </ChartFontsBoundary>
         </PolarChart>
     );
 }

From a79a81c5b5490ce3de4c6fb71b20207682b687b4 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:15:49 -0700
Subject: [PATCH 29/35] refactor(charts): hoist pie label parse and trim font
 types

Parse pie labelcomponent once in VictoryChartPie, import getChartSkiaTypeface
from utils, collapse ChartFontsValue alias, and remove duplicate native provider.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/context/ChartFontsContext.tsx      |  6 ++---
 .../context/ChartFontsProvider.native.tsx     | 25 -------------------
 .../Charts/types/chartFontsTypes.ts           |  4 +--
 .../components/VictoryChartLabel.tsx          |  3 ++-
 .../components/VictoryChartLegend.tsx         |  3 ++-
 .../components/VictoryChartPie.tsx            | 12 ++++++++-
 .../components/VictoryChartPieLabel.tsx       | 24 ++++++++----------
 7 files changed, 30 insertions(+), 47 deletions(-)
 delete mode 100644 src/components/Charts/context/ChartFontsProvider.native.tsx

diff --git a/src/components/Charts/context/ChartFontsContext.tsx b/src/components/Charts/context/ChartFontsContext.tsx
index 56ef96b9511c..7eb079d6e1f2 100644
--- a/src/components/Charts/context/ChartFontsContext.tsx
+++ b/src/components/Charts/context/ChartFontsContext.tsx
@@ -1,11 +1,11 @@
 import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
 import {createContext, useContext} from 'react';
-import type {ChartFontsContextValue} from '@components/Charts/types/chartFontsTypes';
+import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
 import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
 
-const ChartFontsContext = createContext<ChartFontsContextValue | null>(null);
+const ChartFontsContext = createContext<ChartFontsValue | null>(null);
 
-function useChartFontsContext(): ChartFontsContextValue {
+function useChartFontsContext(): ChartFontsValue {
     const context = useContext(ChartFontsContext);
 
     if (!context) {
diff --git a/src/components/Charts/context/ChartFontsProvider.native.tsx b/src/components/Charts/context/ChartFontsProvider.native.tsx
deleted file mode 100644
index 3db66295e728..000000000000
--- a/src/components/Charts/context/ChartFontsProvider.native.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import useChartFonts from '@components/Charts/hooks/useChartFonts';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
-import {ChartFontsContext} from './ChartFontsContext';
-
-type ChartFontsProviderProps = {
-    children: React.ReactNode;
-    value?: ChartFontsValue;
-};
-
-function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
-    const fonts = useChartFonts();
-
-    return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
-}
-
-function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
-    if (value) {
-        return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
-    }
-
-    return <ChartFontsLoaderProvider>{children}</ChartFontsLoaderProvider>;
-}
-
-export default ChartFontsProvider;
diff --git a/src/components/Charts/types/chartFontsTypes.ts b/src/components/Charts/types/chartFontsTypes.ts
index eb55f5ad8c38..e5d7e563c952 100644
--- a/src/components/Charts/types/chartFontsTypes.ts
+++ b/src/components/Charts/types/chartFontsTypes.ts
@@ -6,6 +6,4 @@ type ChartFontsValue = {
     fontMgr: SkTypefaceFontProvider | null;
 };
 
-type ChartFontsContextValue = ChartFontsValue;
-
-export type {ChartFontsContextValue, ChartFontsValue};
+export type {ChartFontsValue};
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
index 702c63dbca90..bc3a2ca25cd6 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLabel.tsx
@@ -1,7 +1,8 @@
 import {Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React from 'react';
-import {getChartSkiaTypeface, useChartTypefaces} from '@components/Charts/hooks';
+import {useChartTypefaces} from '@components/Charts/hooks';
+import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
 import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import computeTextAnchorPosition from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/computeTextAnchorPosition';
 
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
index 6588c6fc9d61..70d654878b68 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartLegend.tsx
@@ -1,7 +1,8 @@
 import {Circle, Skia, Text as SkText} from '@shopify/react-native-skia';
 import type {Color, SkFont} from '@shopify/react-native-skia';
 import React, {Fragment} from 'react';
-import {getChartSkiaTypeface, useChartTypefaces} from '@components/Charts/hooks';
+import {useChartTypefaces} from '@components/Charts/hooks';
+import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
 import type {LegendItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 
 type VictoryChartLegendProps = LegendItem;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 00bfbd3dbe0e..6313a52560b2 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -1,7 +1,10 @@
-import React from 'react';
+import React, {useMemo} from 'react';
+import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import {Pie} from 'victory-native';
+import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
+import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
 import VictoryChartPieLabel from './VictoryChartPieLabel';
 
 type VictoryChartPieProps = {tnode: TNode};
@@ -10,6 +13,12 @@ type VictoryChartPieProps = {tnode: TNode};
 const START_ANGLE = 270;
 
 function VictoryChartPie({tnode}: VictoryChartPieProps) {
+    const renderEngine = useAmbientTRenderEngine();
+    const labelItemTemplate = useMemo(() => {
+        const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
+        return labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
+    }, [tnode.attributes.labelcomponent, renderEngine]);
+
     const innerRadius = tnode.attributes.innerradius !== undefined ? Number(parseAttribute(tnode.attributes.innerradius)) : undefined;
     const radius = tnode.attributes.radius !== undefined ? Number(parseAttribute(tnode.attributes.radius)) : undefined;
     const size = radius ? radius * 2 : undefined;
@@ -25,6 +34,7 @@ function VictoryChartPie({tnode}: VictoryChartPieProps) {
                     <VictoryChartPieLabel
                         tnode={tnode}
                         slice={slice}
+                        labelItemTemplate={labelItemTemplate}
                     />
                 </Pie.Slice>
             )}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
index 4eda84bbfcf1..6b99a8361a74 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPieLabel.tsx
@@ -1,28 +1,23 @@
 import React from 'react';
-import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import type {PieSliceData} from 'victory-native';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
-import parseVictoryLabelNode from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/victoryLabelParser';
-import type {PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
+import type {LabelItem, PolarChartData} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
 import parseAttribute from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseAttribute';
-import parseComponent from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseComponent';
 import VictoryChartLabel from './VictoryChartLabel';
 
 type VictoryChartPieLabelProps = {
     tnode: TNode;
     slice: PieSliceData;
+    labelItemTemplate?: LabelItem;
 };
 
 const RADIAN = Math.PI / 180;
 
-function VictoryChartPieLabel({tnode, slice}: VictoryChartPieLabelProps) {
+function VictoryChartPieLabel({tnode, slice, labelItemTemplate}: VictoryChartPieLabelProps) {
     const {data} = useVictoryChartContext();
-    const renderEngine = useAmbientTRenderEngine();
-    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
-    const labelItem = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
 
-    if (!labelItem) {
+    if (!labelItemTemplate) {
         return null;
     }
 
@@ -35,10 +30,13 @@ function VictoryChartPieLabel({tnode, slice}: VictoryChartPieLabelProps) {
     const x = slice.center.x + labelRadius * Math.cos(-midAngle * RADIAN);
     const y = slice.center.y + labelRadius * Math.sin(midAngle * RADIAN);
 
-    labelItem.text = text;
-    labelItem.x = x;
-    labelItem.y = y;
-    labelItem.textAnchor = 'middle';
+    const labelItem: LabelItem = {
+        ...labelItemTemplate,
+        text,
+        x,
+        y,
+        textAnchor: 'middle',
+    };
 
     return <VictoryChartLabel {...labelItem} />;
 }

From 4b51eee9926905db58517b0c719a5023b21fc87d Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:28:41 -0700
Subject: [PATCH 30/35] chore(charts): fix ESLint violations in chart font
 modules

Satisfy prefer-default-export, prefer-type-fest, no-negated-variables,
and no-restricted-imports in chart font types, assets, and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/context/ChartFontsContext.tsx   | 2 +-
 src/components/Charts/context/ChartFontsProvider.tsx  | 2 +-
 src/components/Charts/hooks/useChartFonts.ts          | 2 +-
 src/components/Charts/types/chartFontsTypes.ts        | 2 +-
 src/components/Charts/utils/chartFontAssets.native.ts | 4 ++--
 src/components/Charts/utils/chartFontAssets.ts        | 4 ++--
 src/components/Charts/utils/chartFontConstants.ts     | 3 ++-
 src/components/Charts/utils/chartFontsCache.ts        | 6 +++---
 tests/unit/getChartSkiaTypefaceTest.ts                | 8 ++++----
 9 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/src/components/Charts/context/ChartFontsContext.tsx b/src/components/Charts/context/ChartFontsContext.tsx
index 7eb079d6e1f2..03ef4db13915 100644
--- a/src/components/Charts/context/ChartFontsContext.tsx
+++ b/src/components/Charts/context/ChartFontsContext.tsx
@@ -1,6 +1,6 @@
 import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
 import {createContext, useContext} from 'react';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
 import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
 
 const ChartFontsContext = createContext<ChartFontsValue | null>(null);
diff --git a/src/components/Charts/context/ChartFontsProvider.tsx b/src/components/Charts/context/ChartFontsProvider.tsx
index 3db66295e728..b90c811285ae 100644
--- a/src/components/Charts/context/ChartFontsProvider.tsx
+++ b/src/components/Charts/context/ChartFontsProvider.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import useChartFonts from '@components/Charts/hooks/useChartFonts';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
 import {ChartFontsContext} from './ChartFontsContext';
 
 type ChartFontsProviderProps = {
diff --git a/src/components/Charts/hooks/useChartFonts.ts b/src/components/Charts/hooks/useChartFonts.ts
index bcfeb445bcce..5b012437a9d1 100644
--- a/src/components/Charts/hooks/useChartFonts.ts
+++ b/src/components/Charts/hooks/useChartFonts.ts
@@ -1,5 +1,5 @@
 import {useEffect, useSyncExternalStore} from 'react';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
 import {getChartFontsSnapshot, loadChartFontsOnce, subscribeToChartFonts} from '@components/Charts/utils/chartFontsCache';
 
 function useChartFonts(): ChartFontsValue {
diff --git a/src/components/Charts/types/chartFontsTypes.ts b/src/components/Charts/types/chartFontsTypes.ts
index e5d7e563c952..deaecc415114 100644
--- a/src/components/Charts/types/chartFontsTypes.ts
+++ b/src/components/Charts/types/chartFontsTypes.ts
@@ -6,4 +6,4 @@ type ChartFontsValue = {
     fontMgr: SkTypefaceFontProvider | null;
 };
 
-export type {ChartFontsValue};
+export default ChartFontsValue;
diff --git a/src/components/Charts/utils/chartFontAssets.native.ts b/src/components/Charts/utils/chartFontAssets.native.ts
index cfb0b204cebd..585fdee494e4 100644
--- a/src/components/Charts/utils/chartFontAssets.native.ts
+++ b/src/components/Charts/utils/chartFontAssets.native.ts
@@ -29,9 +29,9 @@ const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
     CUSTOM_EMOJI_FONT,
 };
 
-const CHART_FONT_MGR_NOTO_ASSETS = {
+const CHART_FONT_MGR_SUPPLEMENTAL_ASSETS = {
     NotoSansSymbols: SANS_SYMBOLS_FONT,
     NotoSansSCMonths: SANS_SC_MONTHS_FONT,
 } as const;
 
-export {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
+export {CHART_FONT_MGR_SUPPLEMENTAL_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontAssets.ts b/src/components/Charts/utils/chartFontAssets.ts
index 1bd1654289d3..a846320d3d7b 100644
--- a/src/components/Charts/utils/chartFontAssets.ts
+++ b/src/components/Charts/utils/chartFontAssets.ts
@@ -30,9 +30,9 @@ const CHART_SKIA_TYPEFACE_ASSETS: Record<ChartSkiaTypefaceKey, DataModule> = {
     CUSTOM_EMOJI_FONT,
 };
 
-const CHART_FONT_MGR_NOTO_ASSETS = {
+const CHART_FONT_MGR_SUPPLEMENTAL_ASSETS = {
     NotoSansSymbols: SANS_SYMBOLS_FONT,
     NotoSansSCMonths: SANS_SC_MONTHS_FONT,
 } as const;
 
-export {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
+export {CHART_FONT_MGR_SUPPLEMENTAL_ASSETS, CHART_SKIA_TYPEFACE_ASSETS};
diff --git a/src/components/Charts/utils/chartFontConstants.ts b/src/components/Charts/utils/chartFontConstants.ts
index 3d98703a116e..a97328a3bf3a 100644
--- a/src/components/Charts/utils/chartFontConstants.ts
+++ b/src/components/Charts/utils/chartFontConstants.ts
@@ -1,9 +1,10 @@
+import type {TupleToUnion} from 'type-fest';
 import type {ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
 
 /** Font families registered on Skia fontMgr and used by Paragraph API multi-font fallback. */
 const CHART_FONT_FAMILY_NAMES = ['ExpensifyNeue', 'ExpensifyMono', 'ExpensifyNewKansas', 'NotoSansSymbols', 'NotoSansSCMonths'] as const;
 
-type ChartFontFamilyName = (typeof CHART_FONT_FAMILY_NAMES)[number];
+type ChartFontFamilyName = TupleToUnion<typeof CHART_FONT_FAMILY_NAMES>;
 
 /** Maps fontMgr family names to already-loaded chart typefaces (Noto families load separately). */
 const CHART_FONT_MGR_FROM_TYPEFACES: Record<Exclude<ChartFontFamilyName, 'NotoSansSymbols' | 'NotoSansSCMonths'>, ChartSkiaTypefaceKey[]> = {
diff --git a/src/components/Charts/utils/chartFontsCache.ts b/src/components/Charts/utils/chartFontsCache.ts
index cb530d49abe1..3945e4d57550 100644
--- a/src/components/Charts/utils/chartFontsCache.ts
+++ b/src/components/Charts/utils/chartFontsCache.ts
@@ -1,10 +1,10 @@
 import type {DataModule, SkTypeface} from '@shopify/react-native-skia';
 import {Skia} from '@shopify/react-native-skia';
 import {Image} from 'react-native';
-import type {ChartFontsValue} from '@components/Charts/types/chartFontsTypes';
+import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
 import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
 import Log from '@libs/Log';
-import {CHART_FONT_MGR_NOTO_ASSETS, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
+import {CHART_FONT_MGR_SUPPLEMENTAL_ASSETS, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
 import {CHART_FONT_MGR_FROM_TYPEFACES} from './chartFontConstants';
 
 const EMPTY_CHART_FONTS: ChartFontsValue = {
@@ -67,7 +67,7 @@ function buildChartFontsValue(typefaces: ChartDefaultTypeface): Promise<ChartFon
     }
 
     return Promise.all(
-        Object.entries(CHART_FONT_MGR_NOTO_ASSETS).map(async ([familyName, asset]) => {
+        Object.entries(CHART_FONT_MGR_SUPPLEMENTAL_ASSETS).map(async ([familyName, asset]) => {
             const typeface = await loadTypefaceFromAsset(asset);
 
             if (typeface) {
diff --git a/tests/unit/getChartSkiaTypefaceTest.ts b/tests/unit/getChartSkiaTypefaceTest.ts
index 9abdcd35843f..c627d98a480f 100644
--- a/tests/unit/getChartSkiaTypefaceTest.ts
+++ b/tests/unit/getChartSkiaTypefaceTest.ts
@@ -1,9 +1,9 @@
 import type {SkTypeface} from '@shopify/react-native-skia';
 import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
+import {CHART_SKIA_TYPEFACE_ASSETS} from '@components/Charts/utils/chartFontAssets';
 import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
-import singleFontFamily from '@styles/utils/FontUtils/fontFamily/singleFontFamily';
 
-const CHART_SKIA_TYPEFACE_KEYS = Object.keys(singleFontFamily).filter((key): key is ChartSkiaTypefaceKey => key !== 'SYSTEM');
+const CHART_SKIA_TYPEFACE_KEYS = Object.keys(CHART_SKIA_TYPEFACE_ASSETS) as ChartSkiaTypefaceKey[];
 
 function makeTypefaces(): ChartDefaultTypeface {
     return Object.fromEntries(CHART_SKIA_TYPEFACE_KEYS.map((key) => [key, {id: key} as unknown as SkTypeface])) as ChartDefaultTypeface;
@@ -38,13 +38,13 @@ describe('getChartSkiaTypeface', () => {
     });
 
     it('should resolve Expensify New Kansas by font family', () => {
-        const typeface = getChartSkiaTypeface(typefaces, {fontFamily: singleFontFamily.EXP_NEW_KANSAS_MEDIUM.fontFamily});
+        const typeface = getChartSkiaTypeface(typefaces, {fontFamily: 'Expensify New Kansas'});
         expect(typeface).toBe(typefaces.EXP_NEW_KANSAS_MEDIUM);
     });
 
     it('should resolve italic Expensify Neue bold to the bold italic typeface', () => {
         const typeface = getChartSkiaTypeface(typefaces, {
-            fontFamily: singleFontFamily.EXP_NEUE.fontFamily,
+            fontFamily: 'Expensify Neue',
             fontStyle: 'italic',
             fontWeight: 'bold',
         });

From 739fbca93e14f4ae270fc9d6bbaada704812e108 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:32:10 -0700
Subject: [PATCH 31/35] refactor(charts): inline ChartFontsProvider in Skia
 overlays

Remove ChartFontsBoundary wrapper and use ChartFontsProvider directly
where Skia subtrees need font context re-established.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../Charts/context/ChartFontsBoundary.tsx        | 16 ----------------
 src/components/Charts/hooks/index.ts             |  1 -
 .../components/VictoryChartCartesian.tsx         |  7 ++++---
 .../components/VictoryChartPolar.tsx             |  7 ++++---
 4 files changed, 8 insertions(+), 23 deletions(-)
 delete mode 100644 src/components/Charts/context/ChartFontsBoundary.tsx

diff --git a/src/components/Charts/context/ChartFontsBoundary.tsx b/src/components/Charts/context/ChartFontsBoundary.tsx
deleted file mode 100644
index d98f346ffc24..000000000000
--- a/src/components/Charts/context/ChartFontsBoundary.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import ChartFontsProvider from './ChartFontsProvider';
-
-type ChartFontsBoundaryProps = {
-    children: React.ReactNode;
-};
-
-/**
- * Re-mounts chart font context inside Skia overlay subtrees (e.g. cartesian renderOutside,
- * polar children) where React context does not propagate across the Skia boundary.
- */
-function ChartFontsBoundary({children}: ChartFontsBoundaryProps) {
-    return <ChartFontsProvider>{children}</ChartFontsProvider>;
-}
-
-export default ChartFontsBoundary;
diff --git a/src/components/Charts/hooks/index.ts b/src/components/Charts/hooks/index.ts
index 4362541d8eae..7ff2f5556252 100644
--- a/src/components/Charts/hooks/index.ts
+++ b/src/components/Charts/hooks/index.ts
@@ -4,7 +4,6 @@ export {default as useChartParagraphs} from './useChartParagraphs';
 export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
 export {useChartFontManager} from '@components/Charts/context/ChartFontsContext';
 export {default as ChartFontsProvider} from '@components/Charts/context/ChartFontsProvider';
-export {default as ChartFontsBoundary} from '@components/Charts/context/ChartFontsBoundary';
 export {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
 export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
 export type {HitTestArgs} from './useChartInteractions';
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
index 4ab6a47ce991..a50e79bf578f 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartCartesian.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {CartesianChart} from 'victory-native';
-import {ChartFontsBoundary} from '@components/Charts/hooks';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -26,7 +26,8 @@ function VictoryChartCartesian() {
             domainPadding={domainPadding}
             padding={padding}
             renderOutside={(renderArgs) => (
-                <ChartFontsBoundary>
+                // Chart font context does not propagate across the Skia renderOutside boundary.
+                <ChartFontsProvider>
                     <VictoryChartRenderArgsProvider value={renderArgs}>
                         {labelItems.map((labelItem) => (
                             <VictoryChartLabel
@@ -41,7 +42,7 @@ function VictoryChartCartesian() {
                             />
                         ))}
                     </VictoryChartRenderArgsProvider>
-                </ChartFontsBoundary>
+                </ChartFontsProvider>
             )}
         >
             {(renderArgs) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
index 5a1a8b068999..65c7e0233380 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPolar.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import {PolarChart} from 'victory-native';
-import {ChartFontsBoundary} from '@components/Charts/hooks';
+import {ChartFontsProvider} from '@components/Charts/hooks';
 import {COLOR_KEY, LABEL_KEY, VALUE_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
 import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
 import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
@@ -21,7 +21,8 @@ function VictoryChartPolar() {
             valueKey={VALUE_KEY}
             colorKey={COLOR_KEY}
         >
-            <ChartFontsBoundary>
+            {/* Chart font context does not propagate into polar Skia children. */}
+            <ChartFontsProvider>
                 {tnode.children.map((child) => (
                     <VictoryChartCategories
                         key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
@@ -40,7 +41,7 @@ function VictoryChartPolar() {
                         {...legendItem}
                     />
                 ))}
-            </ChartFontsBoundary>
+            </ChartFontsProvider>
         </PolarChart>
     );
 }

From 73c102ad06ef2b014eb53564b8b17586427e7c22 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:36:21 -0700
Subject: [PATCH 32/35] chore(charts): drop useMemo from VictoryChartPie

React Compiler handles memoization; parse label template inline.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 .../VictoryChartRenderer/components/VictoryChartPie.tsx   | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
index 6313a52560b2..8226c772ea6a 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx
@@ -1,4 +1,4 @@
-import React, {useMemo} from 'react';
+import React from 'react';
 import {useAmbientTRenderEngine} from 'react-native-render-html';
 import type {TNode} from 'react-native-render-html';
 import {Pie} from 'victory-native';
@@ -14,10 +14,8 @@ const START_ANGLE = 270;
 
 function VictoryChartPie({tnode}: VictoryChartPieProps) {
     const renderEngine = useAmbientTRenderEngine();
-    const labelItemTemplate = useMemo(() => {
-        const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
-        return labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
-    }, [tnode.attributes.labelcomponent, renderEngine]);
+    const labelComponentNode = parseComponent(tnode.attributes.labelcomponent, renderEngine, 'victorylabel');
+    const labelItemTemplate = labelComponentNode ? parseVictoryLabelNode(labelComponentNode).labelItems?.at(0) : undefined;
 
     const innerRadius = tnode.attributes.innerradius !== undefined ? Number(parseAttribute(tnode.attributes.innerradius)) : undefined;
     const radius = tnode.attributes.radius !== undefined ? Number(parseAttribute(tnode.attributes.radius)) : undefined;

From 39940a1bffd01388611558bc4978f6cfaab24499 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:39:15 -0700
Subject: [PATCH 33/35] Fix prettier

---
 tests/unit/components/Charts/VictoryTheme.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/unit/components/Charts/VictoryTheme.test.ts b/tests/unit/components/Charts/VictoryTheme.test.ts
index 7960ec115f6a..78525af7b963 100644
--- a/tests/unit/components/Charts/VictoryTheme.test.ts
+++ b/tests/unit/components/Charts/VictoryTheme.test.ts
@@ -1,5 +1,5 @@
-import type VictoryThemeType from '@components/Charts/VictoryTheme';
 import {CHART_FONT_FAMILY_NAMES} from '@components/Charts/utils/chartFontConstants';
+import type VictoryThemeType from '@components/Charts/VictoryTheme';
 import colors from '@styles/theme/colors';
 
 /**

From 0be9474b0d95e61ebffb67c23aba0200efa6f616 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:42:47 -0700
Subject: [PATCH 34/35] chore(charts): remove unused ChartFontFamilyName export

Fix knip unused type finding; type remains file-local only.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/utils/chartFontConstants.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/Charts/utils/chartFontConstants.ts b/src/components/Charts/utils/chartFontConstants.ts
index a97328a3bf3a..0ac924e91f90 100644
--- a/src/components/Charts/utils/chartFontConstants.ts
+++ b/src/components/Charts/utils/chartFontConstants.ts
@@ -14,4 +14,3 @@ const CHART_FONT_MGR_FROM_TYPEFACES: Record<Exclude<ChartFontFamilyName, 'NotoSa
 };
 
 export {CHART_FONT_FAMILY_NAMES, CHART_FONT_MGR_FROM_TYPEFACES};
-export type {ChartFontFamilyName};

From 3840c5380dde3b8e76e5b5bc2363a08e07f3e2fa Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 3 Jun 2026 12:56:33 -0700
Subject: [PATCH 35/35] fix(charts): use mutable fontFamilies array for Skia
 API

Array.from widens readonly CHART_FONT_FAMILY_NAMES for Paragraph pushStyle.

Co-authored-by: Cursor <cursoragent@cursor.com>
---
 src/components/Charts/VictoryTheme.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Charts/VictoryTheme.ts b/src/components/Charts/VictoryTheme.ts
index a0bcfe21cf0a..30b29a3ab9a6 100644
--- a/src/components/Charts/VictoryTheme.ts
+++ b/src/components/Charts/VictoryTheme.ts
@@ -52,7 +52,7 @@ const VictoryTheme = {
         default: getChartColor(DEFAULT_CHART_COLOR_INDEX),
         getColor: getChartColor,
     },
-    fontFamilies: [...CHART_FONT_FAMILY_NAMES],
+    fontFamilies: Array.from(CHART_FONT_FAMILY_NAMES),
     axis: {
         /** Number of Y-axis ticks (including zero) */
         tickCount: 5,
