From d85eb1a072c3dbb6d4cf6df0b8f7b2ee65e91e18 Mon Sep 17 00:00:00 2001 From: "truph01 (via MelvinBot)" Date: Thu, 25 Jun 2026 08:21:21 +0000 Subject: [PATCH 1/7] A11y: stop auto-closing Report settings pages on selection; add Save button Co-authored-by: truph01 --- .../DynamicNotificationPreferencePage.tsx | 29 +++++--- .../settings/Report/DynamicVisibilityPage.tsx | 67 ++++++++++--------- .../Report/DynamicWriteCapabilityPage.tsx | 31 +++++---- 3 files changed, 73 insertions(+), 54 deletions(-) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index f3b4df745e07..2282600050df 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -1,5 +1,4 @@ -import React, {useCallback} from 'react'; -import type {ValueOf} from 'type-fest'; +import React, {useCallback, useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -25,6 +24,7 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const isMoneyRequest = isMoneyRequestReport(report); const currentNotificationPreference = getReportNotificationPreference(report); + const [selectedNotificationPreference, setSelectedNotificationPreference] = useState(currentNotificationPreference); const shouldDisableNotificationPreferences = isArchivedNonExpenseReport(report, isReportArchived) || isSelfDM(report) || (!isMoneyRequest && isHiddenForCurrentUser(currentNotificationPreference)); const notificationPreferenceOptions = Object.values(CONST.REPORT.NOTIFICATION_PREFERENCE) @@ -33,7 +33,7 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen value: preference, text: translate(`notificationPreferencesPage.notificationPreferences.${preference}`), keyForList: preference, - isSelected: preference === currentNotificationPreference, + isSelected: preference === selectedNotificationPreference, })); const backPath = useDynamicBackPath(DYNAMIC_ROUTES.NOTIFICATION_PREFERENCES.path); @@ -41,12 +41,18 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen Navigation.goBack(backPath); }, [backPath]); - const updateNotificationPreferenceForReportAction = useCallback( - (value: ValueOf) => { - updateNotificationPreference(report.reportID, currentNotificationPreference, value, currentUserAccountID, undefined, undefined); - goBack(); - }, - [report.reportID, currentNotificationPreference, currentUserAccountID, goBack], + const saveNotificationPreference = useCallback(() => { + updateNotificationPreference(report.reportID, currentNotificationPreference, selectedNotificationPreference, currentUserAccountID, undefined, undefined); + goBack(); + }, [report.reportID, currentNotificationPreference, selectedNotificationPreference, currentUserAccountID, goBack]); + + const confirmButtonOptions = useMemo( + () => ({ + showButton: true, + text: translate('common.save'), + onConfirm: saveNotificationPreference, + }), + [saveNotificationPreference, translate], ); return ( @@ -62,9 +68,10 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen updateNotificationPreferenceForReportAction(option.value)} + onSelectRow={(option) => setSelectedNotificationPreference(option.value)} + confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect - initiallyFocusedItemKey={notificationPreferenceOptions.find((locale) => locale.isSelected)?.keyForList} + initiallyFocusedItemKey={currentNotificationPreference} /> diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index ace3e37dd92c..054c511dc9a4 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {ModalActions} from '@components/Modal/Global/ModalContext'; @@ -29,6 +29,8 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { const {showConfirmModal} = useConfirmModal(); + const [selectedVisibility, setSelectedVisibility] = useState(report?.visibility); + const visibilityOptions = useMemo( () => Object.values(CONST.REPORT.VISIBILITY) @@ -38,37 +40,45 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { value: visibilityOption, alternateText: translate(`newRoomPage.${visibilityOption}Description`), keyForList: visibilityOption, - isSelected: visibilityOption === report?.visibility, + isSelected: visibilityOption === selectedVisibility, })), - [translate, report?.visibility], + [translate, selectedVisibility], ); - const goBack = () => { + const goBack = useCallback(() => { Navigation.goBack(backPath); - }; + }, [backPath]); - const changeVisibility = (newVisibility: RoomVisibility) => { - if (!report) { + const saveVisibility = useCallback(async () => { + if (!report || !selectedVisibility) { return; } - updateRoomVisibility(report.reportID, report.visibility, newVisibility); + // Selecting Public is a sensitive change, so it still has to be confirmed before we persist it. + if (selectedVisibility === CONST.REPORT.VISIBILITY.PUBLIC) { + const result = await showConfirmModal({ + title: translate('common.areYouSure'), + prompt: translate('newRoomPage.publicDescription'), + confirmText: translate('common.yes'), + cancelText: translate('common.no'), + shouldShowCancelButton: true, + danger: true, + }); + if (result.action !== ModalActions.CONFIRM) { + return; + } + } + updateRoomVisibility(report.reportID, report.visibility, selectedVisibility); setNavigationActionToMicrotaskQueue(goBack); - }; + }, [report, selectedVisibility, showConfirmModal, translate, goBack]); - const showPublicVisibilityModal = async () => { - const result = await showConfirmModal({ - title: translate('common.areYouSure'), - prompt: translate('newRoomPage.publicDescription'), - confirmText: translate('common.yes'), - cancelText: translate('common.no'), - shouldShowCancelButton: true, - danger: true, - }); - if (result.action !== ModalActions.CONFIRM) { - return; - } - changeVisibility(CONST.REPORT.VISIBILITY.PUBLIC); - }; + const confirmButtonOptions = useMemo( + () => ({ + showButton: true, + text: translate('common.save'), + onConfirm: saveVisibility, + }), + [saveVisibility, translate], + ); return ( { - if (option.value === CONST.REPORT.VISIBILITY.PUBLIC) { - showPublicVisibilityModal(); - return; - } - changeVisibility(option.value); - }} + onSelectRow={(option) => setSelectedVisibility(option.value)} + confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect - initiallyFocusedItemKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList} + initiallyFocusedItemKey={report?.visibility} ListItem={SingleSelectListItem} /> diff --git a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx index b8f86ca373bf..00e82897431e 100644 --- a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx +++ b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx @@ -1,5 +1,4 @@ -import React, {useCallback} from 'react'; -import type {ValueOf} from 'type-fest'; +import React, {useCallback, useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -21,13 +20,14 @@ type DynamicWriteCapabilityPageProps = WithReportOrNotFoundProps; function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPageProps) { const backPath = useDynamicBackPath(DYNAMIC_ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.path); const {translate} = useLocalize(); + const currentWriteCapability = report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; + const [selectedWriteCapability, setSelectedWriteCapability] = useState(currentWriteCapability); const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({ value, text: translate(`writeCapabilityPage.writeCapability.${value}`), keyForList: value, - isSelected: value === (report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL), + isSelected: value === selectedWriteCapability, })); - const selectedOptionKey = writeCapabilityOptions.find((locale) => locale.isSelected)?.keyForList; const isReportArchived = useReportIsArchived(report.reportID); const isAbleToEdit = canEditWriteCapability(report, policy, isReportArchived); @@ -35,12 +35,18 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage Navigation.goBack(backPath); }; - const updateWriteCapability = useCallback( - (newValue: ValueOf) => { - updateWriteCapabilityUtil(report, newValue); - goBack(); - }, - [report, goBack], + const saveWriteCapability = useCallback(() => { + updateWriteCapabilityUtil(report, selectedWriteCapability); + goBack(); + }, [report, selectedWriteCapability, goBack]); + + const confirmButtonOptions = useMemo( + () => ({ + showButton: true, + text: translate('common.save'), + onConfirm: saveWriteCapability, + }), + [saveWriteCapability, translate], ); return ( @@ -57,9 +63,10 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage updateWriteCapability(option.value)} + onSelectRow={(option) => setSelectedWriteCapability(option.value)} + confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect - initiallyFocusedItemKey={selectedOptionKey} + initiallyFocusedItemKey={currentWriteCapability} /> From 7e12cbc0cece0ad68a3a22a35512e28e15c04433 Mon Sep 17 00:00:00 2001 From: "truph01 (via MelvinBot)" Date: Thu, 25 Jun 2026 09:39:33 +0000 Subject: [PATCH 2/7] Address review comments: fall back to live notification preference; drop redundant React Compiler memoization Co-authored-by: truph01 --- .../DynamicNotificationPreferencePage.tsx | 27 ++++++++++--------- .../settings/Report/DynamicVisibilityPage.tsx | 23 +++++++--------- .../Report/DynamicWriteCapabilityPage.tsx | 19 ++++++------- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index 2282600050df..145af54760ca 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -1,4 +1,5 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useState} from 'react'; +import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -24,7 +25,10 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const isMoneyRequest = isMoneyRequestReport(report); const currentNotificationPreference = getReportNotificationPreference(report); - const [selectedNotificationPreference, setSelectedNotificationPreference] = useState(currentNotificationPreference); + // Keep the draft undefined until the user picks a row so we always fall back to the live preference. + // This avoids freezing a stale/partial value (e.g. a defaulted `hidden`) that could be saved over the real one. + const [draftNotificationPreference, setDraftNotificationPreference] = useState | undefined>(undefined); + const selectedNotificationPreference = draftNotificationPreference ?? currentNotificationPreference; const shouldDisableNotificationPreferences = isArchivedNonExpenseReport(report, isReportArchived) || isSelfDM(report) || (!isMoneyRequest && isHiddenForCurrentUser(currentNotificationPreference)); const notificationPreferenceOptions = Object.values(CONST.REPORT.NOTIFICATION_PREFERENCE) @@ -41,19 +45,16 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen Navigation.goBack(backPath); }, [backPath]); - const saveNotificationPreference = useCallback(() => { + const saveNotificationPreference = () => { updateNotificationPreference(report.reportID, currentNotificationPreference, selectedNotificationPreference, currentUserAccountID, undefined, undefined); goBack(); - }, [report.reportID, currentNotificationPreference, selectedNotificationPreference, currentUserAccountID, goBack]); + }; - const confirmButtonOptions = useMemo( - () => ({ - showButton: true, - text: translate('common.save'), - onConfirm: saveNotificationPreference, - }), - [saveNotificationPreference, translate], - ); + const confirmButtonOptions = { + showButton: true, + text: translate('common.save'), + onConfirm: saveNotificationPreference, + }; return ( setSelectedNotificationPreference(option.value)} + onSelectRow={(option) => setDraftNotificationPreference(option.value)} confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect initiallyFocusedItemKey={currentNotificationPreference} diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index 054c511dc9a4..0d74c150aa2e 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {ModalActions} from '@components/Modal/Global/ModalContext'; @@ -45,11 +45,11 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { [translate, selectedVisibility], ); - const goBack = useCallback(() => { + const goBack = () => { Navigation.goBack(backPath); - }, [backPath]); + }; - const saveVisibility = useCallback(async () => { + const saveVisibility = async () => { if (!report || !selectedVisibility) { return; } @@ -69,16 +69,13 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { } updateRoomVisibility(report.reportID, report.visibility, selectedVisibility); setNavigationActionToMicrotaskQueue(goBack); - }, [report, selectedVisibility, showConfirmModal, translate, goBack]); + }; - const confirmButtonOptions = useMemo( - () => ({ - showButton: true, - text: translate('common.save'), - onConfirm: saveVisibility, - }), - [saveVisibility, translate], - ); + const confirmButtonOptions = { + showButton: true, + text: translate('common.save'), + onConfirm: saveVisibility, + }; return ( { + const saveWriteCapability = () => { updateWriteCapabilityUtil(report, selectedWriteCapability); goBack(); - }, [report, selectedWriteCapability, goBack]); + }; - const confirmButtonOptions = useMemo( - () => ({ - showButton: true, - text: translate('common.save'), - onConfirm: saveWriteCapability, - }), - [saveWriteCapability, translate], - ); + const confirmButtonOptions = { + showButton: true, + text: translate('common.save'), + onConfirm: saveWriteCapability, + }; return ( Date: Sat, 27 Jun 2026 06:15:03 +0000 Subject: [PATCH 3/7] Add blank line before comments per review feedback Co-authored-by: truph01 --- src/pages/settings/Report/DynamicNotificationPreferencePage.tsx | 1 + src/pages/settings/Report/DynamicVisibilityPage.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index 145af54760ca..dd0c153313cb 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -25,6 +25,7 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const isMoneyRequest = isMoneyRequestReport(report); const currentNotificationPreference = getReportNotificationPreference(report); + // Keep the draft undefined until the user picks a row so we always fall back to the live preference. // This avoids freezing a stale/partial value (e.g. a defaulted `hidden`) that could be saved over the real one. const [draftNotificationPreference, setDraftNotificationPreference] = useState | undefined>(undefined); diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index 0d74c150aa2e..54f9af99cb46 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -53,6 +53,7 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { if (!report || !selectedVisibility) { return; } + // Selecting Public is a sensitive change, so it still has to be confirmed before we persist it. if (selectedVisibility === CONST.REPORT.VISIBILITY.PUBLIC) { const result = await showConfirmModal({ From 3f2079c87ed21dea200a7aab3327c6a135e66f9e Mon Sep 17 00:00:00 2001 From: "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/issues/comments#get-an-issue-comment\",\"status\":\"404\"} (via MelvinBot)" Date: Sun, 26 Jul 2026 13:48:10 +0000 Subject: [PATCH 4/7] Fall back to live report value until user selects on Visibility and Write capability pages Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com> --- src/pages/settings/Report/DynamicVisibilityPage.tsx | 7 +++++-- src/pages/settings/Report/DynamicWriteCapabilityPage.tsx | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index 7c750b79c613..8e51c5055f5c 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -35,7 +35,10 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { const {showConfirmModal} = useConfirmModal(); - const [selectedVisibility, setSelectedVisibility] = useState(report?.visibility); + // Keep the draft undefined until the user picks a row so we always fall back to the live visibility. + // This avoids freezing a stale value that could be saved over an external update to report.visibility. + const [draftVisibility, setDraftVisibility] = useState(undefined); + const selectedVisibility = draftVisibility ?? report?.visibility; const visibilityOptions = useMemo( () => @@ -97,7 +100,7 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { setSelectedVisibility(option.value)} + onSelectRow={(option) => setDraftVisibility(option.value)} confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect initiallyFocusedItemKey={report?.visibility} diff --git a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx index cdd7fbdc588f..a5baad057e93 100644 --- a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx +++ b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx @@ -17,6 +17,7 @@ import type {WithReportOrNotFoundProps} from '@pages/inbox/report/withReportOrNo import CONST from '@src/CONST'; import {DYNAMIC_ROUTES} from '@src/ROUTES'; +import type {WriteCapability} from '@src/types/onyx/Report'; import React, {useState} from 'react'; @@ -26,7 +27,11 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage const backPath = useDynamicBackPath(DYNAMIC_ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.path); const {translate} = useLocalize(); const currentWriteCapability = report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; - const [selectedWriteCapability, setSelectedWriteCapability] = useState(currentWriteCapability); + + // Keep the draft undefined until the user picks a row so we always fall back to the live write capability. + // This avoids freezing a stale value that could be saved over an external update to report.writeCapability. + const [draftWriteCapability, setDraftWriteCapability] = useState(undefined); + const selectedWriteCapability = draftWriteCapability ?? currentWriteCapability; const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({ value, text: translate(`writeCapabilityPage.writeCapability.${value}`), @@ -65,7 +70,7 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage setSelectedWriteCapability(option.value)} + onSelectRow={(option) => setDraftWriteCapability(option.value)} confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect initiallyFocusedItemKey={currentWriteCapability} From 8fe8710bca3784980f6cea48eb9fe7d68fce092a Mon Sep 17 00:00:00 2001 From: "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/issues/comments#get-an-issue-comment\",\"status\":\"404\"} (via MelvinBot)" Date: Sun, 26 Jul 2026 13:57:05 +0000 Subject: [PATCH 5/7] Add bottom safe-area padding to Save footers on Report settings single-select pages Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com> --- src/pages/settings/Report/DynamicNotificationPreferencePage.tsx | 1 + src/pages/settings/Report/DynamicVisibilityPage.tsx | 1 + src/pages/settings/Report/DynamicWriteCapabilityPage.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index a0d892298c30..8cb2cbc31c4f 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -81,6 +81,7 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect initiallyFocusedItemKey={currentNotificationPreference} + addBottomSafeAreaPadding /> diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index 8e51c5055f5c..2ed05bd8001d 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -105,6 +105,7 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { shouldSingleExecuteRowSelect initiallyFocusedItemKey={report?.visibility} ListItem={SingleSelectListItem} + addBottomSafeAreaPadding /> diff --git a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx index a5baad057e93..33ae1ba0aabf 100644 --- a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx +++ b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx @@ -74,6 +74,7 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage confirmButtonOptions={confirmButtonOptions} shouldSingleExecuteRowSelect initiallyFocusedItemKey={currentWriteCapability} + addBottomSafeAreaPadding /> From a3649ddf6a63dbc23f1c0ea412d49ef2931ad29a Mon Sep 17 00:00:00 2001 From: "truph01 (via MelvinBot)" Date: Sun, 26 Jul 2026 14:02:47 +0000 Subject: [PATCH 6/7] Remove redundant draft-state explanation comments Co-authored-by: truph01 --- src/pages/settings/Report/DynamicNotificationPreferencePage.tsx | 2 -- src/pages/settings/Report/DynamicVisibilityPage.tsx | 2 -- src/pages/settings/Report/DynamicWriteCapabilityPage.tsx | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index 8cb2cbc31c4f..9ca5b0cd453c 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -33,8 +33,6 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen const isMoneyRequest = isMoneyRequestReport(report); const currentNotificationPreference = getReportNotificationPreference(report); - // Keep the draft undefined until the user picks a row so we always fall back to the live preference. - // This avoids freezing a stale/partial value (e.g. a defaulted `hidden`) that could be saved over the real one. const [draftNotificationPreference, setDraftNotificationPreference] = useState | undefined>(undefined); const selectedNotificationPreference = draftNotificationPreference ?? currentNotificationPreference; const shouldDisableNotificationPreferences = diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index 2ed05bd8001d..eba0fae73550 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -35,8 +35,6 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { const {showConfirmModal} = useConfirmModal(); - // Keep the draft undefined until the user picks a row so we always fall back to the live visibility. - // This avoids freezing a stale value that could be saved over an external update to report.visibility. const [draftVisibility, setDraftVisibility] = useState(undefined); const selectedVisibility = draftVisibility ?? report?.visibility; diff --git a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx index 33ae1ba0aabf..113dec33c678 100644 --- a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx +++ b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx @@ -28,8 +28,6 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage const {translate} = useLocalize(); const currentWriteCapability = report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; - // Keep the draft undefined until the user picks a row so we always fall back to the live write capability. - // This avoids freezing a stale value that could be saved over an external update to report.writeCapability. const [draftWriteCapability, setDraftWriteCapability] = useState(undefined); const selectedWriteCapability = draftWriteCapability ?? currentWriteCapability; const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({ From d8abb61f1332ed56e74d083cae80617af4cfb22f Mon Sep 17 00:00:00 2001 From: "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/issues/comments#get-an-issue-comment\",\"status\":\"404\"} (via MelvinBot)" Date: Mon, 27 Jul 2026 02:39:51 +0000 Subject: [PATCH 7/7] Disable Save when the selected value matches the current value Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com> --- src/pages/settings/Report/DynamicNotificationPreferencePage.tsx | 1 + src/pages/settings/Report/DynamicVisibilityPage.tsx | 1 + src/pages/settings/Report/DynamicWriteCapabilityPage.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx index 9ca5b0cd453c..cdb222db5961 100644 --- a/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx +++ b/src/pages/settings/Report/DynamicNotificationPreferencePage.tsx @@ -60,6 +60,7 @@ function DynamicNotificationPreferencePage({report}: DynamicNotificationPreferen showButton: true, text: translate('common.save'), onConfirm: saveNotificationPreference, + isDisabled: selectedNotificationPreference === currentNotificationPreference, }; return ( diff --git a/src/pages/settings/Report/DynamicVisibilityPage.tsx b/src/pages/settings/Report/DynamicVisibilityPage.tsx index eba0fae73550..172a5dd9441b 100644 --- a/src/pages/settings/Report/DynamicVisibilityPage.tsx +++ b/src/pages/settings/Report/DynamicVisibilityPage.tsx @@ -83,6 +83,7 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { showButton: true, text: translate('common.save'), onConfirm: saveVisibility, + isDisabled: selectedVisibility === report?.visibility, }; return ( diff --git a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx index 113dec33c678..e1f828b9852e 100644 --- a/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx +++ b/src/pages/settings/Report/DynamicWriteCapabilityPage.tsx @@ -52,6 +52,7 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage showButton: true, text: translate('common.save'), onConfirm: saveWriteCapability, + isDisabled: selectedWriteCapability === currentWriteCapability, }; return (