diff --git a/static/app/views/navigation/constants.tsx b/static/app/views/navigation/constants.tsx
index 61bd1b81c52a..f5e70784513f 100644
--- a/static/app/views/navigation/constants.tsx
+++ b/static/app/views/navigation/constants.tsx
@@ -34,3 +34,7 @@ export const SETTINGS_PATH_PREFIX = '/settings';
// Scoped by organization so "Back to app" never returns to another organization.
export const getSettingsReturnPathKey = (organizationSlug: string) =>
`settings-return-path:${organizationSlug}`;
+
+// The project the settings rail links to after the user leaves project settings.
+export const getSettingsLastProjectKey = (organizationSlug: string) =>
+ `settings-last-project:${organizationSlug}`;
diff --git a/static/app/views/navigation/index.desktop.spec.tsx b/static/app/views/navigation/index.desktop.spec.tsx
index 23c1b7c12514..08e01d6a2762 100644
--- a/static/app/views/navigation/index.desktop.spec.tsx
+++ b/static/app/views/navigation/index.desktop.spec.tsx
@@ -17,7 +17,10 @@ import {
import {ConfigStore} from 'sentry/stores/configStore';
import {ProjectsStore} from 'sentry/stores/projectsStore';
import {Navigation} from 'sentry/views/navigation';
-import {NAVIGATION_SIDEBAR_COLLAPSED_LOCAL_STORAGE_KEY} from 'sentry/views/navigation/constants';
+import {
+ NAVIGATION_SIDEBAR_COLLAPSED_LOCAL_STORAGE_KEY,
+ getSettingsLastProjectKey,
+} from 'sentry/views/navigation/constants';
import {PrimaryNavigationContextProvider} from 'sentry/views/navigation/primaryNavigationContext';
const ALL_AVAILABLE_FEATURES = [
@@ -472,6 +475,86 @@ describe('desktop navigation', () => {
}
);
+ it('returns to the last selected project after visiting org settings', async () => {
+ const {router} = render(
+
+
+ ,
+ navigationContext({
+ organization: settingsV3,
+ initialRouterConfig: {
+ location: {pathname: '/settings/org-slug/projects/project-slug/'},
+ routes: ['/settings/:orgId/', '/settings/:orgId/projects/:projectId/'],
+ },
+ })
+ );
+
+ await userEvent.click(screen.getByRole('button', {name: 'Select a project'}));
+ await userEvent.click(await screen.findByRole('option', {name: 'second-project'}));
+ expect(router.location.pathname).toBe(
+ '/settings/org-slug/projects/second-project/'
+ );
+
+ await userEvent.click(screen.getByRole('link', {name: 'Org'}));
+ expect(router.location.pathname).toBe('/settings/org-slug/');
+ expect(screen.getByRole('link', {name: 'Project'})).toHaveAttribute(
+ 'href',
+ '/settings/org-slug/projects/second-project/'
+ );
+
+ await userEvent.click(screen.getByRole('link', {name: 'Project'}));
+ expect(router.location.pathname).toBe(
+ '/settings/org-slug/projects/second-project/'
+ );
+ });
+
+ it('links to the remembered project by ID after it was renamed', async () => {
+ localStorage.setItem(getSettingsLastProjectKey('org-slug'), JSON.stringify('999'));
+ ProjectsStore.loadInitialData([
+ ProjectFixture(),
+ ProjectFixture({id: '999', slug: 'renamed-project'}),
+ ]);
+ render(
+
+
+ ,
+ navigationContext({
+ organization: settingsV3,
+ initialRouterConfig: {location: {pathname: '/settings/org-slug/'}},
+ })
+ );
+
+ await waitFor(() =>
+ expect(screen.getByRole('link', {name: 'Project'})).toHaveAttribute(
+ 'href',
+ '/settings/org-slug/projects/renamed-project/'
+ )
+ );
+ });
+
+ it('falls back to the first project when the remembered one is unavailable', async () => {
+ localStorage.setItem(
+ getSettingsLastProjectKey('org-slug'),
+ JSON.stringify('12345')
+ );
+ render(
+
+
+ ,
+ navigationContext({
+ organization: settingsV3,
+ initialRouterConfig: {location: {pathname: '/settings/org-slug/'}},
+ })
+ );
+
+ await waitFor(() =>
+ expect(screen.getByRole('link', {name: 'Project'})).toHaveAttribute(
+ 'href',
+ '/settings/org-slug/projects/project-slug/'
+ )
+ );
+ });
+
it('switches from project key details to the new project key list', async () => {
const {router} = render(
diff --git a/static/app/views/navigation/navigation.tsx b/static/app/views/navigation/navigation.tsx
index 8712f6c9e846..6c2c0481d841 100644
--- a/static/app/views/navigation/navigation.tsx
+++ b/static/app/views/navigation/navigation.tsx
@@ -1,4 +1,11 @@
-import {Fragment, type PropsWithChildren, type RefObject, useMemo, useRef} from 'react';
+import {
+ Fragment,
+ type PropsWithChildren,
+ type RefObject,
+ useEffect,
+ useMemo,
+ useRef,
+} from 'react';
import {mergeProps} from '@react-aria/utils';
import {IconArrow} from '@sentry/icons/arrow';
import {IconBuilding} from '@sentry/icons/building';
@@ -22,6 +29,7 @@ import Feature from 'sentry/components/acl/feature';
import {ErrorBoundary} from 'sentry/components/errorBoundary';
import {Override} from 'sentry/components/override';
import {t} from 'sentry/locale';
+import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useProjects} from 'sentry/utils/useProjects';
import {getReturnPath} from 'sentry/utils/useReturnPath';
@@ -35,6 +43,7 @@ import {
NAVIGATION_SIDEBAR_SECONDARY_WIDTH_LOCAL_STORAGE_KEY,
PRIMARY_SIDEBAR_WIDTH,
SECONDARY_SIDEBAR_WIDTH,
+ getSettingsLastProjectKey,
getSettingsReturnPathKey,
} from 'sentry/views/navigation/constants';
import {
@@ -205,7 +214,22 @@ export function SettingsPrimaryNavigationItems({
}) {
const {projects} = useProjects();
const {scope: activeScope, projectSlug: activeProjectSlug} = useSettingsScope();
- const projectSlug = activeProjectSlug ?? projects[0]?.slug;
+ // Stored by ID, not slug, so renaming the project does not lose it.
+ const [lastProjectId, setLastProjectId] = useLocalStorageState(
+ getSettingsLastProjectKey(organizationSlug),
+ null
+ );
+ const activeProjectId = projects.find(({slug}) => slug === activeProjectSlug)?.id;
+
+ useEffect(() => {
+ if (activeProjectId && activeProjectId !== lastProjectId) {
+ setLastProjectId(activeProjectId);
+ }
+ }, [activeProjectId, lastProjectId, setLastProjectId]);
+
+ // The remembered project can be deleted or lose access since it was stored.
+ const rememberedProjectSlug = projects.find(({id}) => id === lastProjectId)?.slug;
+ const projectSlug = activeProjectSlug ?? rememberedProjectSlug ?? projects[0]?.slug;
const projectSettingsPath = `/settings/${organizationSlug}/projects/`;
const items = [
{