From f159541311971b6f67011ba41c544763201b588f Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 3 Dec 2025 18:28:27 +0100
Subject: [PATCH 01/12] feat: hide troubleshoot on prod, Implement auto-off
 timeout for troubleshoot data recording

---
 src/libs/actions/Troubleshoot.ts              | 25 +++++++++++++++++++
 .../Troubleshoot/TroubleshootPage.tsx         |  2 +-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index 8f149d09d132..7d4a0831a4d6 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -1,11 +1,36 @@
 import Onyx from 'react-native-onyx';
 import ONYXKEYS from '@src/ONYXKEYS';
 
+// Auto-off timeout for troubleshoot recording (10 minutes)
+const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000;
+
+// Module-level timeout reference to persist across component mounts/unmounts
+let autoOffTimeout: NodeJS.Timeout | null = null;
+
+/**
+ * Clear the auto-off timeout if it exists
+ */
+function clearAutoOffTimeout() {
+    if (autoOffTimeout) {
+        clearTimeout(autoOffTimeout);
+        autoOffTimeout = null;
+    }
+}
+
 /**
  * Set whether or not to record troubleshoot data
  * @param shouldRecord Whether or not to record troubleshoot data
  */
 function setShouldRecordTroubleshootData(shouldRecord: boolean) {
+    clearAutoOffTimeout();
+
+    if (shouldRecord) {
+        autoOffTimeout = setTimeout(() => {
+            Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+            autoOffTimeout = null;
+        }, AUTO_OFF_TIMEOUT_MS);
+    }
+
     Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, shouldRecord);
 }
 
diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
index 63ba7a76150e..7f7c4855596c 100644
--- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
+++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
@@ -183,7 +183,7 @@ function TroubleshootPage() {
                     >
                         <View style={[styles.flex1, styles.mt5]}>
                             <View>
-                                <RecordTroubleshootDataToolMenu />
+                                {!isProduction && <RecordTroubleshootDataToolMenu />}
                                 <TestToolRow title={translate('initialSettingsPage.troubleshoot.maskExportOnyxStateData')}>
                                     <Switch
                                         accessibilityLabel={translate('initialSettingsPage.troubleshoot.maskExportOnyxStateData')}

From 1ebafae22a552cba6900bd0d8a637a554e169533 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 3 Dec 2025 18:42:06 +0100
Subject: [PATCH 02/12] fix: lint

---
 src/libs/actions/Troubleshoot.ts | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index 7d4a0831a4d6..a3cc80d63de0 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -11,10 +11,12 @@ let autoOffTimeout: NodeJS.Timeout | null = null;
  * Clear the auto-off timeout if it exists
  */
 function clearAutoOffTimeout() {
-    if (autoOffTimeout) {
-        clearTimeout(autoOffTimeout);
-        autoOffTimeout = null;
+    if (!autoOffTimeout) {
+        return;
     }
+    
+    clearTimeout(autoOffTimeout);
+    autoOffTimeout = null;
 }
 
 /**

From 20bc84890ea6ba315fd2b4e8448694f8eebfc675 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 3 Dec 2025 18:57:02 +0100
Subject: [PATCH 03/12] fix: prettier

---
 src/libs/actions/Troubleshoot.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index a3cc80d63de0..b9ac34eb1990 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -14,7 +14,7 @@ function clearAutoOffTimeout() {
     if (!autoOffTimeout) {
         return;
     }
-    
+
     clearTimeout(autoOffTimeout);
     autoOffTimeout = null;
 }

From 8dacaf6a3dac6217fa77c85fc06ed4421d01aa56 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Thu, 4 Dec 2025 16:17:19 +0100
Subject: [PATCH 04/12] feat: add troubleshoot recording start time and
 refactor recording management functions

---
 src/ONYXKEYS.ts                               |   4 +
 .../BaseRecordTroubleshootDataToolMenu.tsx    |  44 +-----
 src/libs/actions/Troubleshoot.ts              | 146 ++++++++++++++++--
 3 files changed, 148 insertions(+), 46 deletions(-)

diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index ff5685b0cca2..fa36948acd24 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -441,6 +441,9 @@ const ONYXKEYS = {
     /** Indicates whether we should record troubleshoot data or not */
     SHOULD_RECORD_TROUBLESHOOT_DATA: 'shouldRecordTroubleshootData',
 
+    /** Timestamp when troubleshoot recording was started (for auto-off after 10 minutes) */
+    TROUBLESHOOT_RECORDING_START_TIME: 'troubleshootRecordingStartTime',
+
     /** Indicates whether we should mask fragile user data while exporting onyx state or not */
     SHOULD_MASK_ONYX_STATE: 'shouldMaskOnyxState',
 
@@ -1238,6 +1241,7 @@ type OnyxValuesMapping = {
     [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs;
     [ONYXKEYS.SHOULD_STORE_LOGS]: boolean;
     [ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA]: boolean;
+    [ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number;
     [ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean;
     [ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean;
     [ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean;
diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 057060dc12c4..6e05b6c1fece 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -4,7 +4,7 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react';
 import {Alert} from 'react-native';
 import RNFetchBlob from 'react-native-blob-util';
 import DeviceInfo from 'react-native-device-info';
-import {startProfiling, stopProfiling} from 'react-native-release-profiler';
+import {stopProfiling} from 'react-native-release-profiler';
 import Button from '@components/Button';
 import Switch from '@components/Switch';
 import TestToolRow from '@components/TestToolRow';
@@ -12,9 +12,7 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {disableLoggingAndFlushLogs, setShouldStoreLogs} from '@libs/actions/Console';
-import toggleProfileTool from '@libs/actions/ProfilingTool';
-import {setShouldRecordTroubleshootData} from '@libs/actions/Troubleshoot';
+import {disableRecording, enableRecording} from '@libs/actions/Troubleshoot';
 import {parseStringifiedMessages} from '@libs/Console';
 import getPlatform from '@libs/getPlatform';
 import Log from '@libs/Log';
@@ -88,29 +86,12 @@ function BaseRecordTroubleshootDataToolMenu({
     const styles = useThemeStyles();
     const [shouldRecordTroubleshootData] = useOnyx(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, {canBeMissing: true});
     const [capturedLogs] = useOnyx(ONYXKEYS.LOGS, {canBeMissing: true});
-    const [isProfilingInProgress] = useOnyx(ONYXKEYS.APP_PROFILING_IN_PROGRESS, {canBeMissing: true});
     const [shareUrls, setShareUrls] = useState<string[]>();
     const [isDisabled, setIsDisabled] = useState<boolean>(false);
     const [profileTracePath, setProfileTracePath] = useState<string>();
 
     const shouldShowProfileTool = useMemo(() => shouldShowProfileToolUtil(), []);
 
-    const onToggleProfiling = useCallback(() => {
-        const shouldProfiling = !isProfilingInProgress;
-        if (shouldProfiling) {
-            setShareUrls(undefined);
-            Memoize.startMonitoring();
-            Performance.enableMonitoring();
-            startProfiling();
-        } else {
-            Performance.disableMonitoring();
-        }
-        toggleProfileTool(shouldProfiling);
-        return () => {
-            Performance.disableMonitoring();
-        };
-    }, [isProfilingInProgress]);
-
     const getAppInfo = useCallback(() => {
         return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => {
             return JSON.stringify({
@@ -128,12 +109,8 @@ function BaseRecordTroubleshootDataToolMenu({
     const onStopProfiling = useMemo(() => (shouldShowProfileTool ? stopProfiling : () => Promise.resolve()), [shouldShowProfileTool]);
 
     const onToggle = () => {
-        if (shouldShowProfileTool) {
-            onToggleProfiling();
-        }
         if (!shouldRecordTroubleshootData) {
-            setShouldStoreLogs(true);
-            setShouldRecordTroubleshootData(true);
+            enableRecording();
 
             if (onEnableLogging) {
                 onEnableLogging();
@@ -146,8 +123,7 @@ function BaseRecordTroubleshootDataToolMenu({
 
         if (!capturedLogs) {
             Alert.alert(translate('initialSettingsPage.troubleshoot.noLogsToShare'));
-            disableLoggingAndFlushLogs();
-            setShouldRecordTroubleshootData(false);
+            disableRecording();
             return;
         }
 
@@ -162,8 +138,7 @@ function BaseRecordTroubleshootDataToolMenu({
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
+                        disableRecording();
                         setIsDisabled(false);
                         onDownloadZip?.();
                     });
@@ -198,8 +173,7 @@ function BaseRecordTroubleshootDataToolMenu({
                                     zipRef.current?.file(infoFileName, appInfo);
 
                                     onDisableLogging(logsWithParsedMessages).then(() => {
-                                        disableLoggingAndFlushLogs();
-                                        setShouldRecordTroubleshootData(false);
+                                        disableRecording();
                                         setIsDisabled(false);
                                         onDownloadZip?.();
                                     });
@@ -233,8 +207,7 @@ function BaseRecordTroubleshootDataToolMenu({
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
+                        disableRecording();
                         setIsDisabled(false);
                     });
                 });
@@ -246,8 +219,7 @@ function BaseRecordTroubleshootDataToolMenu({
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
+                        disableRecording();
                         setIsDisabled(false);
                     });
                 });
diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index b9ac34eb1990..df53ae815678 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -1,11 +1,22 @@
-import Onyx from 'react-native-onyx';
+import Onyx, {OnyxEntry} from 'react-native-onyx';
+import {startProfiling} from 'react-native-release-profiler';
+import {Memoize} from '@libs/memoize';
+import Performance from '@libs/Performance';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {disableLoggingAndFlushLogs, setShouldStoreLogs} from './Console';
+import toggleProfileTool from './ProfilingTool';
+import {shouldShowProfileTool} from './TestTool';
 
 // Auto-off timeout for troubleshoot recording (10 minutes)
 const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000;
 
-// Module-level timeout reference to persist across component mounts/unmounts
+// Module-level state
 let autoOffTimeout: NodeJS.Timeout | null = null;
+let shouldRecordTroubleshootData: OnyxEntry<boolean>;
+let troubleshootRecordingStartTime: OnyxEntry<number>;
+let isRecordingLoaded = false;
+let isStartTimeLoaded = false;
+let isInitialized = false;
 
 /**
  * Clear the auto-off timeout if it exists
@@ -19,6 +30,122 @@ function clearAutoOffTimeout() {
     autoOffTimeout = null;
 }
 
+/**
+ * Disable troubleshoot recording, stop profiling, and clean up logs.
+ * Used for auto-off and invalid state cleanup.
+ */
+function disableRecording() {
+    clearAutoOffTimeout();
+
+    // Stop profiling and performance monitoring
+    Performance.disableMonitoring();
+    Memoize.stopMonitoring();
+    toggleProfileTool(false);
+
+    // Disable logging and flush logs
+    disableLoggingAndFlushLogs();
+
+    // Update Onyx state
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+}
+
+/**
+ * Schedule auto-off after the given time
+ */
+function scheduleAutoOff(remainingTime: number) {
+    clearAutoOffTimeout();
+    autoOffTimeout = setTimeout(() => {
+        disableRecording();
+        autoOffTimeout = null;
+    }, remainingTime);
+}
+
+/**
+ * Enable troubleshoot recording, start profiling, and enable log storage.
+ */
+function enableRecording() {
+    clearAutoOffTimeout();
+
+    // Enable log storage
+    setShouldStoreLogs(true);
+
+    // Start profiling and performance monitoring if available
+    if (shouldShowProfileTool()) {
+        Memoize.startMonitoring();
+        Performance.enableMonitoring();
+        startProfiling();
+        toggleProfileTool(true);
+    }
+
+    // Update Onyx state and schedule auto-off
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now());
+    scheduleAutoOff(AUTO_OFF_TIMEOUT_MS);
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true);
+}
+
+/**
+ * Check recording state on app load and handle accordingly.
+ * - If recording ON but no start time → disable (invalid state)
+ * - If recording ON and start time exists → calculate remaining time or disable if exceeded
+ */
+function tryInitialize() {
+    if (isInitialized || !isRecordingLoaded || !isStartTimeLoaded) {
+        return;
+    }
+    isInitialized = true;
+
+    if (!shouldRecordTroubleshootData) {
+        return;
+    }
+
+    // Recording is ON but no start time - invalid state, disable
+    if (!troubleshootRecordingStartTime) {
+        disableRecording();
+        return;
+    }
+
+    // Calculate remaining time
+    const elapsedTime = Date.now() - troubleshootRecordingStartTime;
+
+    if (elapsedTime >= AUTO_OFF_TIMEOUT_MS) {
+        // Time exceeded, disable recording
+        disableRecording();
+    } else {
+        // Schedule auto-off for remaining time
+        const remainingTime = AUTO_OFF_TIMEOUT_MS - elapsedTime;
+        scheduleAutoOff(remainingTime);
+    }
+}
+
+/**
+ * Listen for changes to the troubleshoot recording flag.
+ * On app load, triggers initialization to handle auto-off timer.
+ */
+Onyx.connect({
+    key: ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA,
+    initWithStoredValues: true,
+    callback: (value) => {
+        shouldRecordTroubleshootData = value;
+        isRecordingLoaded = true;
+        tryInitialize();
+    },
+});
+
+/**
+ * Listen for changes to the recording start time.
+ * On app load, triggers initialization to calculate remaining time for auto-off.
+ */
+Onyx.connect({
+    key: ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME,
+    initWithStoredValues: true,
+    callback: (value) => {
+        troubleshootRecordingStartTime = value;
+        isStartTimeLoaded = true;
+        tryInitialize();
+    },
+});
+
 /**
  * Set whether or not to record troubleshoot data
  * @param shouldRecord Whether or not to record troubleshoot data
@@ -27,14 +154,13 @@ function setShouldRecordTroubleshootData(shouldRecord: boolean) {
     clearAutoOffTimeout();
 
     if (shouldRecord) {
-        autoOffTimeout = setTimeout(() => {
-            Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
-            autoOffTimeout = null;
-        }, AUTO_OFF_TIMEOUT_MS);
+        Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now());
+        scheduleAutoOff(AUTO_OFF_TIMEOUT_MS);
+        Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true);
+    } else {
+        Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+        Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
     }
-
-    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, shouldRecord);
 }
 
-// eslint-disable-next-line import/prefer-default-export
-export {setShouldRecordTroubleshootData};
+export {setShouldRecordTroubleshootData, enableRecording, disableRecording};

From 75b6559084d2fc31f47e49c72a5e0e2e3412f076 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Thu, 4 Dec 2025 16:39:08 +0100
Subject: [PATCH 05/12] refactor: centralize profiling data handling and
 cleanup in Troubleshoot actions

---
 .../BaseRecordTroubleshootDataToolMenu.tsx    | 72 ++++++++-----------
 src/libs/actions/Troubleshoot.ts              | 48 ++++++++++++-
 2 files changed, 76 insertions(+), 44 deletions(-)

diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 6e05b6c1fece..bb7b7e6bac79 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -1,10 +1,9 @@
 import type JSZip from 'jszip';
 import type {RefObject} from 'react';
-import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import React, {useCallback, useEffect, useState} from 'react';
 import {Alert} from 'react-native';
 import RNFetchBlob from 'react-native-blob-util';
 import DeviceInfo from 'react-native-device-info';
-import {stopProfiling} from 'react-native-release-profiler';
 import Button from '@components/Button';
 import Switch from '@components/Switch';
 import TestToolRow from '@components/TestToolRow';
@@ -12,13 +11,11 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {disableRecording, enableRecording} from '@libs/actions/Troubleshoot';
+import {cleanupAfterDisable, disableRecording, enableRecording, stopProfilingAndGetData} from '@libs/actions/Troubleshoot';
+import type {ProfilingData} from '@libs/actions/Troubleshoot';
 import {parseStringifiedMessages} from '@libs/Console';
 import getPlatform from '@libs/getPlatform';
 import Log from '@libs/Log';
-import {Memoize} from '@libs/memoize';
-import Performance from '@libs/Performance';
-import {shouldShowProfileTool as shouldShowProfileToolUtil} from '@userActions/TestTool';
 import CONFIG from '@src/CONFIG';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -90,9 +87,7 @@ function BaseRecordTroubleshootDataToolMenu({
     const [isDisabled, setIsDisabled] = useState<boolean>(false);
     const [profileTracePath, setProfileTracePath] = useState<string>();
 
-    const shouldShowProfileTool = useMemo(() => shouldShowProfileToolUtil(), []);
-
-    const getAppInfo = useCallback(() => {
+    const getAppInfo = useCallback((profilingData: ProfilingData) => {
         return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => {
             return JSON.stringify({
                 appVersion: pkg.version,
@@ -100,13 +95,11 @@ function BaseRecordTroubleshootDataToolMenu({
                 platform: getPlatform(),
                 totalMemory: formatBytes(totalMemory, 2),
                 usedMemory: formatBytes(usedMemory, 2),
-                memoizeStats: Memoize.stopMonitoring(),
-                performance: shouldShowProfileTool ? Performance.getPerformanceMeasures() : undefined,
+                memoizeStats: profilingData.memoizeStats,
+                performance: profilingData.performanceMeasures,
             });
         });
-    }, [shouldShowProfileTool]);
-
-    const onStopProfiling = useMemo(() => (shouldShowProfileTool ? stopProfiling : () => Promise.resolve()), [shouldShowProfileTool]);
+    }, []);
 
     const onToggle = () => {
         if (!shouldRecordTroubleshootData) {
@@ -132,21 +125,22 @@ function BaseRecordTroubleshootDataToolMenu({
 
         const infoFileName = `App_Info_${pkg.version}.json`;
 
-        if (getPlatform() === CONST.PLATFORM.WEB) {
-            onStopProfiling(true, newFileName).then(() => {
-                getAppInfo().then((appInfo) => {
+        // Stop profiling and get data (centralized in Troubleshoot.ts)
+        stopProfilingAndGetData(newFileName).then((profilingData) => {
+            const {profilePath} = profilingData;
+
+            if (getPlatform() === CONST.PLATFORM.WEB) {
+                getAppInfo(profilingData).then((appInfo) => {
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableRecording();
+                        cleanupAfterDisable();
                         setIsDisabled(false);
                         onDownloadZip?.();
                     });
                 });
-            });
-        } else if (getPlatform() === CONST.PLATFORM.IOS) {
-            onStopProfiling(true, newFileName).then((path) => {
-                if (!path) {
+            } else if (getPlatform() === CONST.PLATFORM.IOS) {
+                if (!profilePath) {
                     return;
                 }
 
@@ -167,13 +161,13 @@ function BaseRecordTroubleshootDataToolMenu({
                         Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
                     })
                     .then(() => {
-                        RNFS.copyFile(path, newFilePath)
+                        RNFS.copyFile(profilePath, newFilePath)
                             .then(() => {
-                                getAppInfo().then((appInfo) => {
+                                getAppInfo(profilingData).then((appInfo) => {
                                     zipRef.current?.file(infoFileName, appInfo);
 
                                     onDisableLogging(logsWithParsedMessages).then(() => {
-                                        disableRecording();
+                                        cleanupAfterDisable();
                                         setIsDisabled(false);
                                         onDownloadZip?.();
                                     });
@@ -190,41 +184,37 @@ function BaseRecordTroubleshootDataToolMenu({
                         console.error('[ProfilingToolMenu] error copying file: ', error);
                         Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
                     });
-            });
-        } else if (getPlatform() === CONST.PLATFORM.ANDROID) {
-            onStopProfiling(true, newFileName).then((path) => {
-                if (!path) {
+            } else if (getPlatform() === CONST.PLATFORM.ANDROID) {
+                if (!profilePath) {
                     return;
                 }
 
                 RNFetchBlob.fs
                     // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
-                    .stat(!path.startsWith('content://media/') && path.match(/\/downloads\/\d+$/) ? `content://media/${path}` : path)
+                    .stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath)
                     .then(({path: realPath}) => setProfileTracePath(realPath))
-                    .catch(() => setProfileTracePath(path));
+                    .catch(() => setProfileTracePath(profilePath));
 
-                getAppInfo().then((appInfo) => {
+                getAppInfo(profilingData).then((appInfo) => {
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableRecording();
+                        cleanupAfterDisable();
                         setIsDisabled(false);
                     });
                 });
-            });
-        } else {
-            // Desktop
-            onStopProfiling(true, newFileName).then(() => {
-                getAppInfo().then((appInfo) => {
+            } else {
+                // Desktop
+                getAppInfo(profilingData).then((appInfo) => {
                     zipRef.current?.file(infoFileName, appInfo);
 
                     onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableRecording();
+                        cleanupAfterDisable();
                         setIsDisabled(false);
                     });
                 });
-            });
-        }
+            }
+        });
     };
 
     useEffect(() => {
diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index df53ae815678..50ebbbe24212 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -1,5 +1,6 @@
-import Onyx, {OnyxEntry} from 'react-native-onyx';
-import {startProfiling} from 'react-native-release-profiler';
+import Onyx from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import {startProfiling, stopProfiling} from 'react-native-release-profiler';
 import {Memoize} from '@libs/memoize';
 import Performance from '@libs/Performance';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -7,6 +8,12 @@ import {disableLoggingAndFlushLogs, setShouldStoreLogs} from './Console';
 import toggleProfileTool from './ProfilingTool';
 import {shouldShowProfileTool} from './TestTool';
 
+type ProfilingData = {
+    profilePath: string | undefined;
+    memoizeStats: ReturnType<typeof Memoize.stopMonitoring>;
+    performanceMeasures: ReturnType<typeof Performance.getPerformanceMeasures> | undefined;
+};
+
 // Auto-off timeout for troubleshoot recording (10 minutes)
 const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000;
 
@@ -30,6 +37,29 @@ function clearAutoOffTimeout() {
     autoOffTimeout = null;
 }
 
+/**
+ * Stop profiling and get profiling data.
+ * Used for manual disable to get profile path and stats before cleanup.
+ * @param fileName - The filename to save the profile trace as
+ * @returns Profile path, memoize stats, and performance measures
+ */
+async function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
+    const showProfileTool = shouldShowProfileTool();
+
+    // Stop profiler and save to file (only if profiling is available)
+    const profilePath = showProfileTool ? await stopProfiling(true, fileName) : undefined;
+
+    // Get stats before stopping monitoring
+    const memoizeStats = Memoize.stopMonitoring();
+    const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
+
+    // Stop monitoring
+    Performance.disableMonitoring();
+    toggleProfileTool(false);
+
+    return {profilePath, memoizeStats, performanceMeasures};
+}
+
 /**
  * Disable troubleshoot recording, stop profiling, and clean up logs.
  * Used for auto-off and invalid state cleanup.
@@ -50,6 +80,17 @@ function disableRecording() {
     Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
 }
 
+/**
+ * Clean up Onyx state and flush logs.
+ * Used after manual disable when profiling was already stopped via stopProfilingAndGetData.
+ */
+function cleanupAfterDisable() {
+    clearAutoOffTimeout();
+    disableLoggingAndFlushLogs();
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+}
+
 /**
  * Schedule auto-off after the given time
  */
@@ -163,4 +204,5 @@ function setShouldRecordTroubleshootData(shouldRecord: boolean) {
     }
 }
 
-export {setShouldRecordTroubleshootData, enableRecording, disableRecording};
+export {setShouldRecordTroubleshootData, enableRecording, disableRecording, stopProfilingAndGetData, cleanupAfterDisable};
+export type {ProfilingData};

From 0d770615a9a8029abcbdf912557fb69980018731 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 10 Dec 2025 20:14:36 +0100
Subject: [PATCH 06/12] fix comments

---
 src/ONYXKEYS.ts                               |   2 +-
 .../BaseRecordTroubleshootDataToolMenu.tsx    | 113 ++++--------------
 .../handleStopRecording.android.ts            |  31 +++++
 .../handleStopRecording.ios.ts                |  57 +++++++++
 .../handleStopRecording.native.ts             |  18 +++
 .../handleStopRecording.types.ts              |  20 ++++
 .../handleStopRecording.web.ts                |  18 +++
 src/libs/actions/Troubleshoot.ts              |  44 +++----
 8 files changed, 190 insertions(+), 113 deletions(-)
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts

diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 4e7378eb774f..a8dbfd1348d4 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -1247,7 +1247,7 @@ type OnyxValuesMapping = {
     [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs;
     [ONYXKEYS.SHOULD_STORE_LOGS]: boolean;
     [ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA]: boolean;
-    [ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number;
+    [ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number | null;
     [ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean;
     [ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean;
     [ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean;
diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index bb7b7e6bac79..7e08e003f7aa 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -2,7 +2,6 @@ import type JSZip from 'jszip';
 import type {RefObject} from 'react';
 import React, {useCallback, useEffect, useState} from 'react';
 import {Alert} from 'react-native';
-import RNFetchBlob from 'react-native-blob-util';
 import DeviceInfo from 'react-native-device-info';
 import Button from '@components/Button';
 import Switch from '@components/Switch';
@@ -15,14 +14,13 @@ import {cleanupAfterDisable, disableRecording, enableRecording, stopProfilingAnd
 import type {ProfilingData} from '@libs/actions/Troubleshoot';
 import {parseStringifiedMessages} from '@libs/Console';
 import getPlatform from '@libs/getPlatform';
-import Log from '@libs/Log';
 import CONFIG from '@src/CONFIG';
-import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Log as OnyxLog} from '@src/types/onyx';
-import pkg from '../../../package.json';
-import RNFS from './RNFS';
+import handleStopRecording from './handleStopRecording';
+import type {StopRecordingParams} from './handleStopRecording.types';
 import Share from './Share';
+import pkg from '../../../package.json';
 
 type File = {
     path: string;
@@ -125,95 +123,26 @@ function BaseRecordTroubleshootDataToolMenu({
 
         const infoFileName = `App_Info_${pkg.version}.json`;
 
-        // Stop profiling and get data (centralized in Troubleshoot.ts)
         stopProfilingAndGetData(newFileName).then((profilingData) => {
-            const {profilePath} = profilingData;
-
-            if (getPlatform() === CONST.PLATFORM.WEB) {
-                getAppInfo(profilingData).then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        cleanupAfterDisable();
-                        setIsDisabled(false);
-                        onDownloadZip?.();
-                    });
+            getAppInfo(profilingData).then((appInfo) => {
+                const params: StopRecordingParams = {
+                    profilingData,
+                    infoFileName,
+                    profileFileName: newFileName,
+                    appInfo,
+                    logsWithParsedMessages,
+                    onDisableLogging,
+                    cleanupAfterDisable,
+                    zipRef,
+                    pathToBeUsed,
+                    onDownloadZip,
+                    setProfileTracePath,
+                };
+
+                handleStopRecording(params).finally(() => {
+                    setIsDisabled(false);
                 });
-            } else if (getPlatform() === CONST.PLATFORM.IOS) {
-                if (!profilePath) {
-                    return;
-                }
-
-                const newFilePath = `${pathToBeUsed}/${newFileName}`;
-
-                RNFS.exists(newFilePath)
-                    .then((fileExists) => {
-                        if (!fileExists) {
-                            return;
-                        }
-
-                        return RNFS.unlink(newFilePath).then(() => {
-                            Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
-                        });
-                    })
-                    .catch((error) => {
-                        const typedError = error as Error;
-                        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
-                    })
-                    .then(() => {
-                        RNFS.copyFile(profilePath, newFilePath)
-                            .then(() => {
-                                getAppInfo(profilingData).then((appInfo) => {
-                                    zipRef.current?.file(infoFileName, appInfo);
-
-                                    onDisableLogging(logsWithParsedMessages).then(() => {
-                                        cleanupAfterDisable();
-                                        setIsDisabled(false);
-                                        onDownloadZip?.();
-                                    });
-                                });
-                                Log.hmmm('[ProfilingToolMenu] file copied successfully');
-
-                                setProfileTracePath(newFilePath);
-                            })
-                            .catch((err) => {
-                                console.error('[ProfilingToolMenu] error copying file: ', err);
-                            });
-                    })
-                    .catch((error: Record<string, unknown>) => {
-                        console.error('[ProfilingToolMenu] error copying file: ', error);
-                        Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
-                    });
-            } else if (getPlatform() === CONST.PLATFORM.ANDROID) {
-                if (!profilePath) {
-                    return;
-                }
-
-                RNFetchBlob.fs
-                    // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
-                    .stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath)
-                    .then(({path: realPath}) => setProfileTracePath(realPath))
-                    .catch(() => setProfileTracePath(profilePath));
-
-                getAppInfo(profilingData).then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        cleanupAfterDisable();
-                        setIsDisabled(false);
-                    });
-                });
-            } else {
-                // Desktop
-                getAppInfo(profilingData).then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        cleanupAfterDisable();
-                        setIsDisabled(false);
-                    });
-                });
-            }
+            });
         });
     };
 
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
new file mode 100644
index 000000000000..904e0c10a476
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
@@ -0,0 +1,31 @@
+import RNFetchBlob from 'react-native-blob-util';
+import type {StopRecordingParams} from './handleStopRecording.types';
+
+export default function handleStopRecording({
+    profilingData,
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+    setProfileTracePath,
+}: StopRecordingParams): Promise<void> {
+    const {profilePath} = profilingData;
+
+    if (profilePath) {
+        RNFetchBlob.fs
+            // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
+            .stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath)
+            .then(({path}) => setProfileTracePath?.(path))
+            .catch(() => setProfileTracePath?.(profilePath));
+    }
+
+    zipRef.current?.file(infoFileName, appInfo);
+
+    return onDisableLogging(logsWithParsedMessages).then(() => {
+        cleanupAfterDisable();
+        onDownloadZip?.();
+    });
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
new file mode 100644
index 000000000000..52b33ec0601d
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
@@ -0,0 +1,57 @@
+import RNFS from 'react-native-fs';
+import Log from '@libs/Log';
+import type {StopRecordingParams} from './handleStopRecording.types';
+
+export default function handleStopRecording({
+    profilingData,
+    infoFileName,
+    profileFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    pathToBeUsed,
+    onDownloadZip,
+    setProfileTracePath,
+}: StopRecordingParams): Promise<void> {
+    const {profilePath} = profilingData;
+
+    if (!profilePath) {
+        cleanupAfterDisable();
+        return Promise.resolve();
+    }
+
+    const newFilePath = `${pathToBeUsed}/${profileFileName}`;
+
+    return RNFS.exists(newFilePath)
+        .then((fileExists) => {
+            if (!fileExists) {
+                return;
+            }
+
+            return RNFS.unlink(newFilePath).then(() => {
+                Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
+            });
+        })
+        .catch((error) => {
+            const typedError = error as Error;
+            Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
+        })
+        .then(() => RNFS.copyFile(profilePath, newFilePath))
+        .then(() => {
+            zipRef.current?.file(infoFileName, appInfo);
+
+            return onDisableLogging(logsWithParsedMessages).then(() => {
+                cleanupAfterDisable();
+                onDownloadZip?.();
+            });
+        })
+        .then(() => {
+            setProfileTracePath?.(newFilePath);
+            Log.hmmm('[ProfilingToolMenu] file copied successfully');
+        })
+        .catch((error) => {
+            Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
+        });
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
new file mode 100644
index 000000000000..04f58a4b8c08
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
@@ -0,0 +1,18 @@
+import type {StopRecordingParams} from './handleStopRecording.types';
+
+export default function handleStopRecording({
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+}: StopRecordingParams): Promise<void> {
+    zipRef.current?.file(infoFileName, appInfo);
+
+    return onDisableLogging(logsWithParsedMessages).then(() => {
+        cleanupAfterDisable();
+        onDownloadZip?.();
+    });
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
new file mode 100644
index 000000000000..e199aa3dbb9b
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
@@ -0,0 +1,20 @@
+import type JSZip from 'jszip';
+import type {RefObject} from 'react';
+import type {ProfilingData} from '@libs/actions/Troubleshoot';
+import type {Log as OnyxLog} from '@src/types/onyx';
+
+type StopRecordingParams = {
+    profilingData: ProfilingData;
+    infoFileName: string;
+    profileFileName: string;
+    appInfo: string;
+    logsWithParsedMessages: OnyxLog[];
+    onDisableLogging: (logs: OnyxLog[]) => Promise<void>;
+    cleanupAfterDisable: () => void;
+    zipRef: RefObject<InstanceType<typeof JSZip>>;
+    pathToBeUsed: string;
+    onDownloadZip?: () => void;
+    setProfileTracePath?: (path: string) => void;
+};
+
+export type {StopRecordingParams};
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
new file mode 100644
index 000000000000..04f58a4b8c08
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
@@ -0,0 +1,18 @@
+import type {StopRecordingParams} from './handleStopRecording.types';
+
+export default function handleStopRecording({
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+}: StopRecordingParams): Promise<void> {
+    zipRef.current?.file(infoFileName, appInfo);
+
+    return onDisableLogging(logsWithParsedMessages).then(() => {
+        cleanupAfterDisable();
+        onDownloadZip?.();
+    });
+}
diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index 50ebbbe24212..3ca4c72d9521 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -9,9 +9,9 @@ import toggleProfileTool from './ProfilingTool';
 import {shouldShowProfileTool} from './TestTool';
 
 type ProfilingData = {
-    profilePath: string | undefined;
+    profilePath?: string;
     memoizeStats: ReturnType<typeof Memoize.stopMonitoring>;
-    performanceMeasures: ReturnType<typeof Performance.getPerformanceMeasures> | undefined;
+    performanceMeasures?: ReturnType<typeof Performance.getPerformanceMeasures>;
 };
 
 // Auto-off timeout for troubleshoot recording (10 minutes)
@@ -20,7 +20,7 @@ const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000;
 // Module-level state
 let autoOffTimeout: NodeJS.Timeout | null = null;
 let shouldRecordTroubleshootData: OnyxEntry<boolean>;
-let troubleshootRecordingStartTime: OnyxEntry<number>;
+let troubleshootRecordingStartTime: OnyxEntry<number | null>;
 let isRecordingLoaded = false;
 let isStartTimeLoaded = false;
 let isInitialized = false;
@@ -43,27 +43,34 @@ function clearAutoOffTimeout() {
  * @param fileName - The filename to save the profile trace as
  * @returns Profile path, memoize stats, and performance measures
  */
-async function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
+function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
     const showProfileTool = shouldShowProfileTool();
 
     // Stop profiler and save to file (only if profiling is available)
-    const profilePath = showProfileTool ? await stopProfiling(true, fileName) : undefined;
+    const profilingPromise = showProfileTool ? stopProfiling(true, fileName) : Promise.resolve(undefined);
 
-    // Get stats before stopping monitoring
-    const memoizeStats = Memoize.stopMonitoring();
-    const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
+    return profilingPromise.then((profilePath) => {
+        // Get stats before stopping monitoring
+        const memoizeStats = Memoize.stopMonitoring();
+        const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
 
-    // Stop monitoring
-    Performance.disableMonitoring();
-    toggleProfileTool(false);
+        // Stop monitoring
+        Performance.disableMonitoring();
+        toggleProfileTool(false);
 
-    return {profilePath, memoizeStats, performanceMeasures};
+        return {profilePath, memoizeStats, performanceMeasures};
+    });
 }
 
 /**
  * Disable troubleshoot recording, stop profiling, and clean up logs.
  * Used for auto-off and invalid state cleanup.
  */
+function clearRecordingOnyxState() {
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+}
+
 function disableRecording() {
     clearAutoOffTimeout();
 
@@ -76,8 +83,7 @@ function disableRecording() {
     disableLoggingAndFlushLogs();
 
     // Update Onyx state
-    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
-    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+    clearRecordingOnyxState();
 }
 
 /**
@@ -87,8 +93,7 @@ function disableRecording() {
 function cleanupAfterDisable() {
     clearAutoOffTimeout();
     disableLoggingAndFlushLogs();
-    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
-    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+    clearRecordingOnyxState();
 }
 
 /**
@@ -163,7 +168,7 @@ function tryInitialize() {
  * Listen for changes to the troubleshoot recording flag.
  * On app load, triggers initialization to handle auto-off timer.
  */
-Onyx.connect({
+Onyx.connectWithoutView({
     key: ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA,
     initWithStoredValues: true,
     callback: (value) => {
@@ -177,7 +182,7 @@ Onyx.connect({
  * Listen for changes to the recording start time.
  * On app load, triggers initialization to calculate remaining time for auto-off.
  */
-Onyx.connect({
+Onyx.connectWithoutView({
     key: ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME,
     initWithStoredValues: true,
     callback: (value) => {
@@ -199,8 +204,7 @@ function setShouldRecordTroubleshootData(shouldRecord: boolean) {
         scheduleAutoOff(AUTO_OFF_TIMEOUT_MS);
         Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true);
     } else {
-        Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
-        Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+        clearRecordingOnyxState();
     }
 }
 

From 751337ec21e2e6293e5f5df84afe7b4beedd9c21 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 10 Dec 2025 20:28:51 +0100
Subject: [PATCH 07/12] Refactor handleStopRecording functions to use
 async/await for improved readability and error handling

---
 .../BaseRecordTroubleshootDataToolMenu.tsx    | 66 +++++++++----------
 .../handleStopRecording.android.ts            | 21 +++---
 .../handleStopRecording.ios.ts                | 54 +++++++--------
 .../handleStopRecording.native.ts             |  9 ++-
 .../handleStopRecording.web.ts                |  9 ++-
 src/libs/actions/Troubleshoot.ts              | 20 +++---
 6 files changed, 86 insertions(+), 93 deletions(-)

diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 7e08e003f7aa..3b54fde1927f 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -85,21 +85,20 @@ function BaseRecordTroubleshootDataToolMenu({
     const [isDisabled, setIsDisabled] = useState<boolean>(false);
     const [profileTracePath, setProfileTracePath] = useState<string>();
 
-    const getAppInfo = useCallback((profilingData: ProfilingData) => {
-        return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => {
-            return JSON.stringify({
-                appVersion: pkg.version,
-                environment: CONFIG.ENVIRONMENT,
-                platform: getPlatform(),
-                totalMemory: formatBytes(totalMemory, 2),
-                usedMemory: formatBytes(usedMemory, 2),
-                memoizeStats: profilingData.memoizeStats,
-                performance: profilingData.performanceMeasures,
-            });
+    const getAppInfo = useCallback(async (profilingData: ProfilingData) => {
+        const [totalMemory, usedMemory] = await Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]);
+        return JSON.stringify({
+            appVersion: pkg.version,
+            environment: CONFIG.ENVIRONMENT,
+            platform: getPlatform(),
+            totalMemory: formatBytes(totalMemory, 2),
+            usedMemory: formatBytes(usedMemory, 2),
+            memoizeStats: profilingData.memoizeStats,
+            performance: profilingData.performanceMeasures,
         });
     }, []);
 
-    const onToggle = () => {
+    const onToggle = async () => {
         if (!shouldRecordTroubleshootData) {
             enableRecording();
 
@@ -123,27 +122,28 @@ function BaseRecordTroubleshootDataToolMenu({
 
         const infoFileName = `App_Info_${pkg.version}.json`;
 
-        stopProfilingAndGetData(newFileName).then((profilingData) => {
-            getAppInfo(profilingData).then((appInfo) => {
-                const params: StopRecordingParams = {
-                    profilingData,
-                    infoFileName,
-                    profileFileName: newFileName,
-                    appInfo,
-                    logsWithParsedMessages,
-                    onDisableLogging,
-                    cleanupAfterDisable,
-                    zipRef,
-                    pathToBeUsed,
-                    onDownloadZip,
-                    setProfileTracePath,
-                };
-
-                handleStopRecording(params).finally(() => {
-                    setIsDisabled(false);
-                });
-            });
-        });
+        try {
+            const profilingData = await stopProfilingAndGetData(newFileName);
+            const appInfo = await getAppInfo(profilingData);
+
+            const params: StopRecordingParams = {
+                profilingData,
+                infoFileName,
+                profileFileName: newFileName,
+                appInfo,
+                logsWithParsedMessages,
+                onDisableLogging,
+                cleanupAfterDisable,
+                zipRef,
+                pathToBeUsed,
+                onDownloadZip,
+                setProfileTracePath,
+            };
+
+            await handleStopRecording(params);
+        } finally {
+            setIsDisabled(false);
+        }
     };
 
     useEffect(() => {
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
index 904e0c10a476..5b50fa52bcc8 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
@@ -1,7 +1,7 @@
 import RNFetchBlob from 'react-native-blob-util';
 import type {StopRecordingParams} from './handleStopRecording.types';
 
-export default function handleStopRecording({
+export default async function handleStopRecording({
     profilingData,
     infoFileName,
     appInfo,
@@ -15,17 +15,20 @@ export default function handleStopRecording({
     const {profilePath} = profilingData;
 
     if (profilePath) {
-        RNFetchBlob.fs
+        try {
             // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
-            .stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath)
-            .then(({path}) => setProfileTracePath?.(path))
-            .catch(() => setProfileTracePath?.(profilePath));
+            const {path} = await RNFetchBlob.fs.stat(
+                !profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath,
+            );
+            setProfileTracePath?.(path);
+        } catch {
+            setProfileTracePath?.(profilePath);
+        }
     }
 
     zipRef.current?.file(infoFileName, appInfo);
 
-    return onDisableLogging(logsWithParsedMessages).then(() => {
-        cleanupAfterDisable();
-        onDownloadZip?.();
-    });
+    await onDisableLogging(logsWithParsedMessages);
+    cleanupAfterDisable();
+    onDownloadZip?.();
 }
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
index 52b33ec0601d..d14ff4b57e5e 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
@@ -2,7 +2,7 @@ import RNFS from 'react-native-fs';
 import Log from '@libs/Log';
 import type {StopRecordingParams} from './handleStopRecording.types';
 
-export default function handleStopRecording({
+export default async function handleStopRecording({
     profilingData,
     infoFileName,
     profileFileName,
@@ -19,39 +19,33 @@ export default function handleStopRecording({
 
     if (!profilePath) {
         cleanupAfterDisable();
-        return Promise.resolve();
+        return;
     }
 
     const newFilePath = `${pathToBeUsed}/${profileFileName}`;
 
-    return RNFS.exists(newFilePath)
-        .then((fileExists) => {
-            if (!fileExists) {
-                return;
-            }
+    try {
+        const fileExists = await RNFS.exists(newFilePath);
+        if (fileExists) {
+            await RNFS.unlink(newFilePath);
+            Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
+        }
+    } catch (error) {
+        const typedError = error as Error;
+        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
+    }
+
+    try {
+        await RNFS.copyFile(profilePath, newFilePath);
+        zipRef.current?.file(infoFileName, appInfo);
 
-            return RNFS.unlink(newFilePath).then(() => {
-                Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
-            });
-        })
-        .catch((error) => {
-            const typedError = error as Error;
-            Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
-        })
-        .then(() => RNFS.copyFile(profilePath, newFilePath))
-        .then(() => {
-            zipRef.current?.file(infoFileName, appInfo);
+        await onDisableLogging(logsWithParsedMessages);
+        cleanupAfterDisable();
+        onDownloadZip?.();
 
-            return onDisableLogging(logsWithParsedMessages).then(() => {
-                cleanupAfterDisable();
-                onDownloadZip?.();
-            });
-        })
-        .then(() => {
-            setProfileTracePath?.(newFilePath);
-            Log.hmmm('[ProfilingToolMenu] file copied successfully');
-        })
-        .catch((error) => {
-            Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
-        });
+        setProfileTracePath?.(newFilePath);
+        Log.hmmm('[ProfilingToolMenu] file copied successfully');
+    } catch (error) {
+        Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
+    }
 }
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
index 04f58a4b8c08..537bcca33842 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
@@ -1,6 +1,6 @@
 import type {StopRecordingParams} from './handleStopRecording.types';
 
-export default function handleStopRecording({
+export default async function handleStopRecording({
     infoFileName,
     appInfo,
     logsWithParsedMessages,
@@ -11,8 +11,7 @@ export default function handleStopRecording({
 }: StopRecordingParams): Promise<void> {
     zipRef.current?.file(infoFileName, appInfo);
 
-    return onDisableLogging(logsWithParsedMessages).then(() => {
-        cleanupAfterDisable();
-        onDownloadZip?.();
-    });
+    await onDisableLogging(logsWithParsedMessages);
+    cleanupAfterDisable();
+    onDownloadZip?.();
 }
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
index 04f58a4b8c08..537bcca33842 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
@@ -1,6 +1,6 @@
 import type {StopRecordingParams} from './handleStopRecording.types';
 
-export default function handleStopRecording({
+export default async function handleStopRecording({
     infoFileName,
     appInfo,
     logsWithParsedMessages,
@@ -11,8 +11,7 @@ export default function handleStopRecording({
 }: StopRecordingParams): Promise<void> {
     zipRef.current?.file(infoFileName, appInfo);
 
-    return onDisableLogging(logsWithParsedMessages).then(() => {
-        cleanupAfterDisable();
-        onDownloadZip?.();
-    });
+    await onDisableLogging(logsWithParsedMessages);
+    cleanupAfterDisable();
+    onDownloadZip?.();
 }
diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index 3ca4c72d9521..4b483c69627b 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -43,23 +43,21 @@ function clearAutoOffTimeout() {
  * @param fileName - The filename to save the profile trace as
  * @returns Profile path, memoize stats, and performance measures
  */
-function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
+async function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
     const showProfileTool = shouldShowProfileTool();
 
     // Stop profiler and save to file (only if profiling is available)
-    const profilingPromise = showProfileTool ? stopProfiling(true, fileName) : Promise.resolve(undefined);
+    const profilePath = showProfileTool ? await stopProfiling(true, fileName) : undefined;
 
-    return profilingPromise.then((profilePath) => {
-        // Get stats before stopping monitoring
-        const memoizeStats = Memoize.stopMonitoring();
-        const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
+    // Get stats before stopping monitoring
+    const memoizeStats = Memoize.stopMonitoring();
+    const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
 
-        // Stop monitoring
-        Performance.disableMonitoring();
-        toggleProfileTool(false);
+    // Stop monitoring
+    Performance.disableMonitoring();
+    toggleProfileTool(false);
 
-        return {profilePath, memoizeStats, performanceMeasures};
-    });
+    return {profilePath, memoizeStats, performanceMeasures};
 }
 
 /**

From 473031c24e7c801a6f36f722b4b00271241ee929 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 10 Dec 2025 21:55:30 +0100
Subject: [PATCH 08/12] fix lint

---
 .../BaseRecordTroubleshootDataToolMenu.tsx    | 14 ++++++----
 .../handleStopRecording.android.ts            |  6 ++---
 .../handleStopRecording.ios.ts                |  9 ++++---
 .../handleStopRecording.native.ts             |  2 +-
 .../handleStopRecording.ts                    | 26 +++++++++++++++++++
 .../handleStopRecording.types.ts              |  2 +-
 .../handleStopRecording.web.ts                |  2 +-
 7 files changed, 45 insertions(+), 16 deletions(-)
 create mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts

diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 3b54fde1927f..4f2aec7a2a4a 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -17,10 +17,10 @@ import getPlatform from '@libs/getPlatform';
 import CONFIG from '@src/CONFIG';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Log as OnyxLog} from '@src/types/onyx';
+import pkg from '../../../package.json';
 import handleStopRecording from './handleStopRecording';
-import type {StopRecordingParams} from './handleStopRecording.types';
+import type StopRecordingParams from './handleStopRecording.types';
 import Share from './Share';
-import pkg from '../../../package.json';
 
 type File = {
     path: string;
@@ -141,9 +141,11 @@ function BaseRecordTroubleshootDataToolMenu({
             };
 
             await handleStopRecording(params);
-        } finally {
-            setIsDisabled(false);
+        } catch (error) {
+            console.error('[ProfilingToolMenu] error handling stop recording', error);
         }
+
+        setIsDisabled(false);
     };
 
     useEffect(() => {
@@ -166,7 +168,9 @@ function BaseRecordTroubleshootDataToolMenu({
                 <Switch
                     accessibilityLabel={translate('initialSettingsPage.troubleshoot.recordTroubleshootData')}
                     isOn={!!shouldRecordTroubleshootData}
-                    onToggle={onToggle}
+                    onToggle={() => {
+                        onToggle().catch((error) => console.error('[ProfilingToolMenu] toggle failed', error));
+                    }}
                     disabled={isDisabled}
                 />
             </TestToolRow>
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
index 5b50fa52bcc8..c71430f76425 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
@@ -1,5 +1,5 @@
 import RNFetchBlob from 'react-native-blob-util';
-import type {StopRecordingParams} from './handleStopRecording.types';
+import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
     profilingData,
@@ -17,9 +17,7 @@ export default async function handleStopRecording({
     if (profilePath) {
         try {
             // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
-            const {path} = await RNFetchBlob.fs.stat(
-                !profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath,
-            );
+            const {path} = await RNFetchBlob.fs.stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath);
             setProfileTracePath?.(path);
         } catch {
             setProfileTracePath?.(profilePath);
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
index d14ff4b57e5e..5a18c1ea5511 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
@@ -1,6 +1,6 @@
 import RNFS from 'react-native-fs';
 import Log from '@libs/Log';
-import type {StopRecordingParams} from './handleStopRecording.types';
+import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
     profilingData,
@@ -31,8 +31,8 @@ export default async function handleStopRecording({
             Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
         }
     } catch (error) {
-        const typedError = error as Error;
-        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
+        const message = error instanceof Error ? error.message : String(error);
+        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', message);
     }
 
     try {
@@ -46,6 +46,7 @@ export default async function handleStopRecording({
         setProfileTracePath?.(newFilePath);
         Log.hmmm('[ProfilingToolMenu] file copied successfully');
     } catch (error) {
-        Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
+        const message = error instanceof Error ? error.message : String(error);
+        Log.hmmm('[ProfilingToolMenu] error copying file: ', message);
     }
 }
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
index 537bcca33842..5351abb9b1f0 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
@@ -1,4 +1,4 @@
-import type {StopRecordingParams} from './handleStopRecording.types';
+import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
     infoFileName,
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
new file mode 100644
index 000000000000..d236f02f4589
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
@@ -0,0 +1,26 @@
+import {Platform} from 'react-native';
+import handleStopRecordingAndroid from './handleStopRecording.android';
+import handleStopRecordingIOS from './handleStopRecording.ios';
+import handleStopRecordingNative from './handleStopRecording.native';
+import type StopRecordingParams from './handleStopRecording.types';
+import handleStopRecordingWeb from './handleStopRecording.web';
+
+type HandleStopRecording = (params: StopRecordingParams) => Promise<void>;
+
+const handleStopRecording: HandleStopRecording = (params) => {
+    if (Platform.OS === 'ios') {
+        return handleStopRecordingIOS(params);
+    }
+
+    if (Platform.OS === 'android') {
+        return handleStopRecordingAndroid(params);
+    }
+
+    if (Platform.OS === 'web') {
+        return handleStopRecordingWeb(params);
+    }
+
+    return handleStopRecordingNative(params);
+};
+
+export default handleStopRecording;
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
index e199aa3dbb9b..19eebde4df72 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
@@ -17,4 +17,4 @@ type StopRecordingParams = {
     setProfileTracePath?: (path: string) => void;
 };
 
-export type {StopRecordingParams};
+export default StopRecordingParams;
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
index 537bcca33842..5351abb9b1f0 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
@@ -1,4 +1,4 @@
-import type {StopRecordingParams} from './handleStopRecording.types';
+import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
     infoFileName,

From 10ed019621b68e492d39da90c016b334118247e4 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Thu, 11 Dec 2025 18:20:40 +0100
Subject: [PATCH 09/12] fix issue with troubleshoot on/off failure

---
 src/libs/ExportOnyxState/index.native.ts | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/libs/ExportOnyxState/index.native.ts b/src/libs/ExportOnyxState/index.native.ts
index 12cfb612ab1c..8e7e5ad04193 100644
--- a/src/libs/ExportOnyxState/index.native.ts
+++ b/src/libs/ExportOnyxState/index.native.ts
@@ -7,9 +7,18 @@ import type OnyxState from '@src/types/onyx/OnyxState';
 import {maskOnyxState} from './common';
 import type {ExportOnyxStateModule, ReadFromOnyxDatabase, ShareAsFile} from './types';
 
+let onyxDb: ReturnType<typeof open> | null = null;
+
+function getOnyxDb() {
+    if (!onyxDb) {
+        onyxDb = open({name: CONST.DEFAULT_DB_NAME});
+    }
+    return onyxDb;
+}
+
 const readFromOnyxDatabase: ReadFromOnyxDatabase = () =>
     new Promise((resolve) => {
-        const db = open({name: CONST.DEFAULT_DB_NAME});
+        const db = getOnyxDb();
         const query = `SELECT * FROM ${CONST.DEFAULT_TABLE_NAME}`;
 
         db.executeAsync<OnyxSQLiteKeyValuePair>(query, []).then(({rows}) => {

From c06db73729fa375c1a3ac53be70428fcae399f00 Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 17 Dec 2025 18:14:07 +0100
Subject: [PATCH 10/12] fix comments

---
 ...ing.native.ts => finalizeStopRecording.ts} |  9 ++++-
 .../handleStopRecording.android.ts            | 15 ++++---
 .../handleStopRecording.ios.ts                | 15 ++++---
 .../handleStopRecording.ts                    | 39 +++++++++----------
 .../handleStopRecording.web.ts                | 17 --------
 5 files changed, 46 insertions(+), 49 deletions(-)
 rename src/components/RecordTroubleshootDataToolMenu/{handleStopRecording.native.ts => finalizeStopRecording.ts} (54%)
 delete mode 100644 src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts

diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
similarity index 54%
rename from src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
rename to src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
index 5351abb9b1f0..f2b695eb9697 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.native.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
@@ -1,6 +1,11 @@
 import type StopRecordingParams from './handleStopRecording.types';
 
-export default async function handleStopRecording({
+type FinalizeStopRecordingParams = Pick<
+    StopRecordingParams,
+    'infoFileName' | 'appInfo' | 'logsWithParsedMessages' | 'onDisableLogging' | 'cleanupAfterDisable' | 'zipRef' | 'onDownloadZip'
+>;
+
+export default async function finalizeStopRecording({
     infoFileName,
     appInfo,
     logsWithParsedMessages,
@@ -8,7 +13,7 @@ export default async function handleStopRecording({
     cleanupAfterDisable,
     zipRef,
     onDownloadZip,
-}: StopRecordingParams): Promise<void> {
+}: FinalizeStopRecordingParams): Promise<void> {
     zipRef.current?.file(infoFileName, appInfo);
 
     await onDisableLogging(logsWithParsedMessages);
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
index c71430f76425..194f9810b989 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
@@ -1,4 +1,5 @@
 import RNFetchBlob from 'react-native-blob-util';
+import finalizeStopRecording from './finalizeStopRecording';
 import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
@@ -24,9 +25,13 @@ export default async function handleStopRecording({
         }
     }
 
-    zipRef.current?.file(infoFileName, appInfo);
-
-    await onDisableLogging(logsWithParsedMessages);
-    cleanupAfterDisable();
-    onDownloadZip?.();
+    await finalizeStopRecording({
+        infoFileName,
+        appInfo,
+        logsWithParsedMessages,
+        onDisableLogging,
+        cleanupAfterDisable,
+        zipRef,
+        onDownloadZip,
+    });
 }
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
index 5a18c1ea5511..1f4a28d17953 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
@@ -1,5 +1,6 @@
 import RNFS from 'react-native-fs';
 import Log from '@libs/Log';
+import finalizeStopRecording from './finalizeStopRecording';
 import type StopRecordingParams from './handleStopRecording.types';
 
 export default async function handleStopRecording({
@@ -37,11 +38,15 @@ export default async function handleStopRecording({
 
     try {
         await RNFS.copyFile(profilePath, newFilePath);
-        zipRef.current?.file(infoFileName, appInfo);
-
-        await onDisableLogging(logsWithParsedMessages);
-        cleanupAfterDisable();
-        onDownloadZip?.();
+        await finalizeStopRecording({
+            infoFileName,
+            appInfo,
+            logsWithParsedMessages,
+            onDisableLogging,
+            cleanupAfterDisable,
+            zipRef,
+            onDownloadZip,
+        });
 
         setProfileTracePath?.(newFilePath);
         Log.hmmm('[ProfilingToolMenu] file copied successfully');
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
index d236f02f4589..09e101799b10 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
@@ -1,26 +1,25 @@
-import {Platform} from 'react-native';
-import handleStopRecordingAndroid from './handleStopRecording.android';
-import handleStopRecordingIOS from './handleStopRecording.ios';
-import handleStopRecordingNative from './handleStopRecording.native';
+import finalizeStopRecording from './finalizeStopRecording';
 import type StopRecordingParams from './handleStopRecording.types';
-import handleStopRecordingWeb from './handleStopRecording.web';
 
 type HandleStopRecording = (params: StopRecordingParams) => Promise<void>;
 
-const handleStopRecording: HandleStopRecording = (params) => {
-    if (Platform.OS === 'ios') {
-        return handleStopRecordingIOS(params);
-    }
-
-    if (Platform.OS === 'android') {
-        return handleStopRecordingAndroid(params);
-    }
-
-    if (Platform.OS === 'web') {
-        return handleStopRecordingWeb(params);
-    }
-
-    return handleStopRecordingNative(params);
-};
+const handleStopRecording: HandleStopRecording = ({
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+}) =>
+    finalizeStopRecording({
+        infoFileName,
+        appInfo,
+        logsWithParsedMessages,
+        onDisableLogging,
+        cleanupAfterDisable,
+        zipRef,
+        onDownloadZip,
+    });
 
 export default handleStopRecording;
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
deleted file mode 100644
index 5351abb9b1f0..000000000000
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.web.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type StopRecordingParams from './handleStopRecording.types';
-
-export default async function handleStopRecording({
-    infoFileName,
-    appInfo,
-    logsWithParsedMessages,
-    onDisableLogging,
-    cleanupAfterDisable,
-    zipRef,
-    onDownloadZip,
-}: StopRecordingParams): Promise<void> {
-    zipRef.current?.file(infoFileName, appInfo);
-
-    await onDisableLogging(logsWithParsedMessages);
-    cleanupAfterDisable();
-    onDownloadZip?.();
-}

From 371ff8bdf0f3d676783e689b503732b8beca12bf Mon Sep 17 00:00:00 2001
From: Yauheni Pasiukevich <pasyukevich@live.com>
Date: Wed, 17 Dec 2025 18:30:04 +0100
Subject: [PATCH 11/12] fix prettier and compiler

---
 .../BaseRecordTroubleshootDataToolMenu.tsx             |  6 +++---
 .../finalizeStopRecording.ts                           |  5 +----
 .../handleStopRecording.ts                             | 10 +---------
 3 files changed, 5 insertions(+), 16 deletions(-)

diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 4771d95a289c..ca4b46d94a9a 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -1,6 +1,6 @@
 import type JSZip from 'jszip';
 import type {RefObject} from 'react';
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {Alert} from 'react-native';
 import DeviceInfo from 'react-native-device-info';
 import Button from '@components/Button';
@@ -85,7 +85,7 @@ function BaseRecordTroubleshootDataToolMenu({
     const [isDisabled, setIsDisabled] = useState<boolean>(false);
     const [profileTracePath, setProfileTracePath] = useState<string>();
 
-    const getAppInfo = useCallback(async (profilingData: ProfilingData) => {
+    const getAppInfo = async (profilingData: ProfilingData) => {
         const [totalMemory, usedMemory] = await Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]);
         return JSON.stringify({
             appVersion: pkg.version,
@@ -96,7 +96,7 @@ function BaseRecordTroubleshootDataToolMenu({
             memoizeStats: profilingData.memoizeStats,
             performance: profilingData.performanceMeasures,
         });
-    }, []);
+    };
 
     const onToggle = async () => {
         if (!shouldRecordTroubleshootData) {
diff --git a/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
index f2b695eb9697..7fb670ff1bfd 100644
--- a/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
@@ -1,9 +1,6 @@
 import type StopRecordingParams from './handleStopRecording.types';
 
-type FinalizeStopRecordingParams = Pick<
-    StopRecordingParams,
-    'infoFileName' | 'appInfo' | 'logsWithParsedMessages' | 'onDisableLogging' | 'cleanupAfterDisable' | 'zipRef' | 'onDownloadZip'
->;
+type FinalizeStopRecordingParams = Pick<StopRecordingParams, 'infoFileName' | 'appInfo' | 'logsWithParsedMessages' | 'onDisableLogging' | 'cleanupAfterDisable' | 'zipRef' | 'onDownloadZip'>;
 
 export default async function finalizeStopRecording({
     infoFileName,
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
index 09e101799b10..90f32b3b584a 100644
--- a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
@@ -3,15 +3,7 @@ import type StopRecordingParams from './handleStopRecording.types';
 
 type HandleStopRecording = (params: StopRecordingParams) => Promise<void>;
 
-const handleStopRecording: HandleStopRecording = ({
-    infoFileName,
-    appInfo,
-    logsWithParsedMessages,
-    onDisableLogging,
-    cleanupAfterDisable,
-    zipRef,
-    onDownloadZip,
-}) =>
+const handleStopRecording: HandleStopRecording = ({infoFileName, appInfo, logsWithParsedMessages, onDisableLogging, cleanupAfterDisable, zipRef, onDownloadZip}) =>
     finalizeStopRecording({
         infoFileName,
         appInfo,

From ddec1752e79948c0a3e13acc7afbe584467c41a3 Mon Sep 17 00:00:00 2001
From: Stephanie Elliott <31225194+stephanieelliott@users.noreply.github.com>
Date: Thu, 18 Dec 2025 18:05:22 -1000
Subject: [PATCH 12/12] Add files via upload

---
 docs/assets/images/new-expensify-rules.png | Bin 0 -> 277377 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 docs/assets/images/new-expensify-rules.png

diff --git a/docs/assets/images/new-expensify-rules.png b/docs/assets/images/new-expensify-rules.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d37df0ed95b4125363c43811cd8233505cc753c
GIT binary patch
