Repository: Automattic/wp-calypso
Pull request #103494: STATS-69: Navigation: Allow go back to Posts & Pages Summary page
Author: login: Nikschavan
Labels: name: [Feature] Stats
description: Everything related to our analytics product at /stats/
Created: 2025-05-16T12:15:53Z | Merged: 2025-05-21T11:16:17Z

<!--
Link a related Github/Linear issue to this PR. If the PR does not immediately resolve the issue,
for example, it requires a separate deployment to production, avoid
using the "fixes" keyword. Consider using "part of" instead.
-->

Part of STATS-69

## Proposed Changes

* Introduce a new hook `useStatsNavigationHistory()` to track the screens user goes to
* Introduce a helper function `recordCurrentScreen()` to record the current screen in the history stack
* Introduce a helper function `popCurrentScreenFromHistory()` to pop the latest item from the history stack, to be used with back buttons.

## Why are these changes being made?
<!--
It's easy to see what a PR does but much harder to find out why it was made,
particularly when researching old changes in history. Record an explanation of
the motivation behind this change and how it will help.
-->

* To allow the user to go back to the "Posts & pages" summary screen

## Testing Instructions

<!--
Add as many details as possible to help others reproduce the issue and test the fix.
"Before / After" screenshots can also be very helpful when the change is visual.
-->

* Open stats traffic page and use any date filters
* Open "Posts & Pages" summary
* Open any post details by clicking on the posts
* Now the back buttons should be able to get user back to the traffic page with correct "back button text" and back URL with correct query params

https://github.com/user-attachments/assets/f622a878-f073-4350-9d86-e86003c6d6bd


## Pre-merge Checklist

<!--
Complete applicable items on this checklist **before** merging into trunk. Inapplicable items can be left unchecked.

Both the PR author and reviewer are responsible for ensuring the checklist is completed.
-->

- [x] Has the general commit checklist been followed? (PCYsg-hS-p2)
- [ ] [Have you written new tests](https://wpcalypso.wordpress.com/devdocs/docs/testing/index.md) for your changes?
- [x] Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
- [x] Have you checked for TypeScript, React or other console errors?
- [x] Have you used memoizing on expensive computations? More info in [Memoizing with create-selector](https://github.com/Automattic/wp-calypso/blob/trunk/packages/state-utils/src/create-selector/README.md) and [Using memoizing selectors](https://react-redux.js.org/api/hooks#using-memoizing-selectors) and [Our Approach to Data](https://github.com/Automattic/wp-calypso/blob/trunk/docs/our-approach-to-data.md)
- [ ] Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    - [ ] For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages. 
- [ ] For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?



Diff:
diff --git a/client/components/navigation-header/navigation-header.scss b/client/components/navigation-header/navigation-header.scss
index c10d4073b6f7..c0698e73abb9 100644
--- a/client/components/navigation-header/navigation-header.scss
+++ b/client/components/navigation-header/navigation-header.scss
@@ -49,6 +49,7 @@
 		text-decoration: none;
 		font-size: $default-font-size;
 		transition: color 0.2s ease;
+		cursor: pointer;
 
 		&:hover {
 			color: var(--wp-components-color-gray-900, $gray-900);
diff --git a/client/components/navigation-header/navigation-header.tsx b/client/components/navigation-header/navigation-header.tsx
index 26b12fa2c999..a9c378a40a91 100644
--- a/client/components/navigation-header/navigation-header.tsx
+++ b/client/components/navigation-header/navigation-header.tsx
@@ -1,13 +1,15 @@
+import page from '@automattic/calypso-router';
 import clsx from 'clsx';
 import { translate } from 'i18n-calypso';
 import { ReactNode } from 'react';
+import { popCurrentScreenFromHistory } from 'calypso/my-sites/stats/hooks/use-stats-navigation-history';
 import './navigation-header.scss';
 
 // Type definitions for the props
 interface BackLinkProps {
 	url?: string;
 	text?: string;
-	onBackClick?: ( e: React.MouseEvent< HTMLAnchorElement > ) => void;
+	onBackClick?: () => void;
 }
 
 export interface HeaderProps extends React.HTMLAttributes< HTMLElement > {
@@ -42,18 +44,22 @@ const NavigationHeader: React.FC< HeaderProps > = ( {
 	backLinkProps,
 	titleElement,
 	headElement = backLinkProps?.url && (
-		<a
+		<button
 			className="calypso-navigation-header__back-link"
-			href={ backLinkProps?.url }
-			onClick={ ( e ) => {
+			type="button"
+			aria-label={ backLinkProps?.text || translate( 'Back' ) }
+			onClick={ () => {
+				popCurrentScreenFromHistory();
+
 				if ( backLinkProps?.onBackClick ) {
-					e.preventDefault();
-					backLinkProps.onBackClick( e );
+					backLinkProps.onBackClick();
+				} else if ( backLinkProps?.url ) {
+					page( backLinkProps.url );
 				}
 			} }
 		>
 			← { backLinkProps?.text ?? translate( 'Back' ) }
-		</a>
+		</button>
 	),
 	rightSection,
 	hasScreenOptionsTab,
diff --git a/client/components/navigation-header/stories/navigation-header.stories.tsx b/client/components/navigation-header/stories/navigation-header.stories.tsx
index a76b1429187b..47a64f59bd1c 100644
--- a/client/components/navigation-header/stories/navigation-header.stories.tsx
+++ b/client/components/navigation-header/stories/navigation-header.stories.tsx
@@ -90,8 +90,7 @@ export const WithScreenOptions: Story = {
 		backLinkProps: {
 			url: '/dashboard',
 			text: 'Back to Dashboard',
-			onBackClick: ( e ) => {
-				e.preventDefault();
+			onBackClick: () => {
 				alert( 'Back button clicked!' );
 			},
 		},
diff --git a/client/my-sites/stats/hooks/use-stats-navigation-history.ts b/client/my-sites/stats/hooks/use-stats-navigation-history.ts
new file mode 100644
index 000000000000..24a31ddfbc01
--- /dev/null
+++ b/client/my-sites/stats/hooks/use-stats-navigation-history.ts
@@ -0,0 +1,187 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/{period}/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/{period}/',
+	posts: '/stats/{period}/posts/',
+	authors: '/stats/{period}/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const localizedTabNames: { [ key: string ]: string | null } = useMemo(
+		() => ( {
+			traffic: translate( 'Traffic' ),
+			insights: translate( 'Insights' ),
+			store: translate( 'Store' ),
+			ads: translate( 'Ads' ),
+			subscribers: translate( 'Subscribers' ),
+			posts: translate( 'Posts & pages' ),
+			authors: translate( 'Authors' ),
+			postDetails: null,
+		} ),
+		[]
+	);
+
+	const [ lastScreen, setLastScreen ] = useState< {
+		screen: string;
+		queryParams: QueryArgs;
+		period: string | null;
+	} >( {
+		screen: defaultLastScreen,
+		queryParams: {},
+		period: 'day',
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+			// Select the second last item from the history stack as the back link.
+			// The last item in the stack if the current screen.
+			const lastItem =
+				Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem && lastItem.screen ) {
+				setLastScreen( lastItem );
+			} else {
+				setLastScreen( {
+					screen: defaultLastScreen,
+					queryParams: {},
+					period: 'day',
+				} );
+			}
+		} catch ( e ) {}
+	}, [] );
+
+	const backLink = useMemo( () => {
+		if ( ! siteSlug ) {
+			return null;
+		}
+
+		let backLink = possibleBackLinks[ lastScreen.screen ];
+
+		if ( ! backLink ) {
+			return null;
+		}
+
+		if ( backLink.includes( '{period}' ) && lastScreen.period ) {
+			backLink = backLink.replace( '{period}', lastScreen.period );
+		}
+
+		const queryParams = buildQueryString( getFilteredQueryParams( lastScreen.queryParams ) );
+
+		return backLink + siteSlug + ( queryParams ? '?' + queryParams : '' );
+	}, [ lastScreen, siteSlug ] );
+
+	return {
+		text: localizedTabNames[ lastScreen.screen ] || '',
+		url: backLink,
+	};
+};
+
+/**
+ * Utility to record the current screen for back navigation
+ * @param {string} screen - Current screen identifier
+ * @param {Object} args - Arguments for the screen
+ * @param {Object} args.queryParams - Query parameters for the screen
+ * @param {string} args.period - Period for the screen
+ * @param {boolean} reset - Whether to reset the navigation history
+ */
+export const recordCurrentScreen = (
+	screen: string,
+	args: {
+		queryParams: QueryArgs;
+		period: string | null;
+	} = {
+		queryParams: {},
+		period: null,
+	},
+	reset: boolean = false
+): void => {
+	try {
+		if ( ! screen || ! ( screen in possibleBackLinks ) ) {
+			return;
+		}
+
+		const filteredQueryParams = getFilteredQueryParams( args.queryParams );
+		const currentEntry = {
+			screen,
+			queryParams: filteredQueryParams,
+			period: args.period,
+		};
+
+		// Get current navigation history array
+		let navigationHistory = reset
+			? []
+			: JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+		// Ensure navigationHistory is an array
+		if ( ! Array.isArray( navigationHistory ) ) {
+			navigationHistory = [];
+		}
+
+		// If the history already has the same screen, remove it
+		if (
+			navigationHistory.some(
+				( entry: { screen: string } ) => entry.screen === currentEntry.screen
+			)
+		) {
+			navigationHistory = navigationHistory.filter(
+				( entry: { screen: string } ) => entry.screen !== currentEntry.screen
+			);
+		}
+
+		navigationHistory.push( currentEntry );
+		sessionStorage.setItem( STORAGE_KEY, JSON.stringify( navigationHistory ) );
+	} catch ( e ) {}
+};
+
+/**
+ * Utility to pop the current screen from the navigation history
+ */
+export const popCurrentScreenFromHistory = (): void => {
+	try {
+		const navigationHistory = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+		navigationHistory.pop();
+		sessionStorage.setItem( STORAGE_KEY, JSON.stringify( navigationHistory ) );
+	} catch ( e ) {}
+};
diff --git a/client/my-sites/stats/site.jsx b/client/my-sites/stats/site.jsx
index 329667e3a545..bf8d85e238f6 100644
--- a/client/my-sites/stats/site.jsx
+++ b/client/my-sites/stats/site.jsx
@@ -38,6 +38,7 @@ import {
 import { getMomentSiteZone } from 'calypso/my-sites/stats/hooks/use-moment-site-zone';
 import useNoticeVisibilityMutation from 'calypso/my-sites/stats/hooks/use-notice-visibility-mutation';
 import { useNoticeVisibilityQuery } from 'calypso/my-sites/stats/hooks/use-notice-visibility-query';
+import { recordCurrentScreen } from 'calypso/my-sites/stats/hooks/use-stats-navigation-history';
 import { getChartRangeParams } from 'calypso/my-sites/stats/utils';
 import {
 	recordGoogleEvent,
@@ -899,7 +900,11 @@ const StatsBodyAccessCheck = ( props ) => {
 };
 
 const StatsSite = ( props ) => {
-	const { period } = props.period;
+	const {
+		context,
+		period: { period },
+	} = props;
+
 	const isOdysseyStats = config.isEnabled( 'is_running_in_jetpack_site' );
 	const siteId = useSelector( getSelectedSiteId );
 	const isJetpack = useSelector( ( state ) => isJetpackSite( state, siteId ) );
@@ -911,6 +916,17 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: context.query,
+				period: period,
+			},
+			true
+		);
+	}, [ context.query, period ] );
+
 	return (
 		<Main fullWidthLayout ariaLabel={ STATS_PRODUCT_NAME }>
 			{ /* Odyssey: Google Business Profile pages are currently unsupported. */ }
diff --git a/client/my-sites/stats/stats-post-detail/index.jsx b/client/my-sites/stats/stats-post-detail/index.jsx
index 434cb8f50029..e31c1d57543e 100644
--- a/client/my-sites/stats/stats-post-detail/index.jsx
+++ b/client/my-sites/stats/stats-post-detail/index.jsx
@@ -4,7 +4,7 @@ import { localizeUrl } from '@automattic/i18n-utils';
 import { Button as CoreButton } from '@wordpress/components';
 import clsx from 'clsx';
 import { localize } from 'i18n-calypso';
-import { flowRight } from 'lodash';
+import { isEqual, flowRight } from 'lodash';
 import PropTypes from 'prop-types';
 import { Component } from 'react';
 import { connect } from 'react-redux';
@@ -19,6 +19,10 @@ import WebPreview from 'calypso/components/web-preview';
 import { decodeEntities, stripHTML } from 'calypso/lib/formatting';
 import PageHeader from 'calypso/my-sites/stats/components/headers/page-header';
 import Main from 'calypso/my-sites/stats/components/stats-main';
+import {
+	useStatsNavigationHistory,
+	recordCurrentScreen,
+} from 'calypso/my-sites/stats/hooks/use-stats-navigation-history';
 import StatsDetailsNavigation from 'calypso/my-sites/stats/stats-details-navigation';
 import { getSitePost, getPostPreviewUrl } from 'calypso/state/posts/selectors';
 import { countPostLikes } from 'calypso/state/posts/selectors/count-post-likes';
@@ -51,6 +55,10 @@ class StatsPostDetail extends Component {
 		siteSlug: PropTypes.string,
 		showViewLink: PropTypes.bool,
 		previewUrl: PropTypes.string,
+		lastScreen: PropTypes.shape( {
+			text: PropTypes.string,
+			url: PropTypes.string,
+		} ),
 	};
 
 	state = {
@@ -92,6 +100,22 @@ class StatsPostDetail extends Component {
 
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context } = this.props;
+		recordCurrentScreen( 'postDetails', {
+			queryParams: context.query,
+			period: null,
+		} );
+	}
+
+	componentDidUpdate( prevProps ) {
+		const { context } = this.props;
+		if ( ! isEqual( prevProps.context, this.props.context ) ) {
+			recordCurrentScreen( 'postDetails', {
+				queryParams: context.query,
+				period: null,
+			} );
+		}
 	}
 
 	openPreview = () => {
@@ -181,6 +205,7 @@ class StatsPostDetail extends Component {
 			isSubscriptionsModuleActive,
 			supportsEmailStats,
 			isSimple,
+			lastScreen,
 		} = this.props;
 
 		const isLoading = isRequestingStats && ! countViews;
@@ -211,9 +236,10 @@ class StatsPostDetail extends Component {
 		const navigationItems = this.getNavigationItemsWithTitle( this.getTitle() );
 
 		const backLinkProps = {
-			text: navigationItems[ 0 ].label,
-			url: navigationItems[ 0 ].href,
+			text: lastScreen.text,
+			url: lastScreen.url,
 		};
+
 		const titleProps = {
 			title: navigationItems[ 1 ].label,
 			// Remove the default logo for Odyssey stats.
@@ -319,6 +345,11 @@ class StatsPostDetail extends Component {
 	}
 }
 
+const StatsPostDetailWrapper = ( props ) => {
+	const lastScreen = useStatsNavigationHistory();
+	return <StatsPostDetail { ...props } lastScreen={ lastScreen } />;
+};
+
 const connectComponent = connect( ( state, { postId } ) => {
 	const siteId = getSelectedSiteId( state );
 	const isJetpack = isJetpackSite( state, siteId );
@@ -347,4 +378,4 @@ const connectComponent = connect( ( state, { postId } ) => {
 	};
 } );
 
-export default flowRight( connectComponent, localize )( StatsPostDetail );
+export default flowRight( connectComponent, localize )( StatsPostDetailWrapper );
diff --git a/client/my-sites/stats/summary/index.jsx b/client/my-sites/stats/summary/index.jsx
index e7af836129c2..a93378e26031 100644
--- a/client/my-sites/stats/summary/index.jsx
+++ b/client/my-sites/stats/summary/index.jsx
@@ -1,6 +1,6 @@
 import config, { isEnabled } from '@automattic/calypso-config';
 import { localize } from 'i18n-calypso';
-import { merge } from 'lodash';
+import { isEqual, merge } from 'lodash';
 import { Component, Fragment } from 'react';
 import { connect } from 'react-redux';
 import titlecase from 'to-title-case';
@@ -16,6 +16,10 @@ import StatsModuleDownloads from 'calypso/my-sites/stats/features/modules/stats-
 import StatsModuleReferrers from 'calypso/my-sites/stats/features/modules/stats-referrers';
 import StatsModuleSearch from 'calypso/my-sites/stats/features/modules/stats-search';
 import StatsModuleTopPosts from 'calypso/my-sites/stats/features/modules/stats-top-posts';
+import {
+	useStatsNavigationHistory,
+	recordCurrentScreen,
+} from 'calypso/my-sites/stats/hooks/use-stats-navigation-history';
 import getMediaItem from 'calypso/state/selectors/get-media-item';
 import getEnvStatsFeatureSupportChecks from 'calypso/state/sites/selectors/get-env-stats-feature-supports';
 import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors';
@@ -43,6 +47,28 @@ const StatsStrings = statsStringsFactory();
 class StatsSummary extends Component {
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context, period } = this.props;
+		const { module } = context.params;
+		const { query } = context;
+
+		recordCurrentScreen( module, {
+			queryParams: query,
+			period: period?.period,
+		} );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( ! isEqual( prevProps.context.query, this.props.context.query ) ) {
+			const { context, period } = this.props;
+			const { module } = context.params;
+			const { query } = context;
+
+			recordCurrentScreen( module, {
+				queryParams: query,
+				period: period?.period,
+			} );
+		}
 	}
 
 	renderSummaryHeader( path, statType, hideNavigation, query ) {
@@ -67,8 +93,14 @@ class StatsSummary extends Component {
 	}
 
 	render() {
-		const { translate, statsQueryOptions, siteId, supportsUTMStats, shouldGateStatsCsvDownload } =
-			this.props;
+		const {
+			translate,
+			statsQueryOptions,
+			siteId,
+			supportsUTMStats,
+			shouldGateStatsCsvDownload,
+			lastScreen,
+		} = this.props;
 		const summaryViews = [];
 		let title;
 		let summaryView;
@@ -387,8 +419,8 @@ class StatsSummary extends Component {
 							className="stats__section-header modernized-header"
 							titleProps={ { title, titleLogo: null } }
 							backLinkProps={ {
-								url: backLink,
-								text: backLabel,
+								url: lastScreen.url,
+								text: lastScreen.text,
 							} }
 							rightSection={
 								<div className="stats-module__header-nav-button">
@@ -459,6 +491,11 @@ class StatsSummary extends Component {
 	}
 }
 
+const StatsSummaryWrapper = ( props ) => {
+	const lastScreen = useStatsNavigationHistory();
+	return <StatsSummary { ...props } lastScreen={ lastScreen } />;
+};
+
 export default connect( ( state, { context, postId } ) => {
 	const siteId = getSelectedSiteId( state );
 
@@ -471,4 +508,4 @@ export default connect( ( state, { context, postId } ) => {
 		supportsUTMStats,
 		shouldGateStatsCsvDownload: shouldGateStats( state, siteId, STATS_FEATURE_DOWNLOAD_CSV ),
 	};
-} )( localize( StatsSummary ) );
+} )( localize( StatsSummaryWrapper ) );


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a new navigation history mechanism to let users return to the "Posts & pages" summary screen by tracking their visited screens. Key changes include:
- Adding the hook and helper functions (useStatsNavigationHistory, recordCurrentScreen, popCurrentScreenFromHistory) to manage navigation history.
- Updating StatsSummary, StatsPostDetail, StatsEmailDetail, and related components to utilize the new navigation history.
- Adjusting navigation header behavior to pop the current screen from the history.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                                                 | Description                                                          |
|----------------------------------------------------------------------|----------------------------------------------------------------------|
| client/my-sites/stats/summary/index.jsx                               | Adds navigation tracking via recordCurrentScreen and new hook usage.  |
| client/my-sites/stats/stats-post-detail/index.jsx                     | Updates screen recording and navigation links using lastScreen prop.  |
| client/my-sites/stats/stats-email-detail/index.jsx                    | Minor update for navigation props consistency.                        |
| client/my-sites/stats/site.jsx                                        | Adds navigation recording in a useEffect for the stats 'traffic' page. |
| client/my-sites/stats/hooks/use-stats-navigation.ts                   | Introduces the core navigation history hook and helper functions.     |
| client/components/navigation-header/navigation-header.tsx             | Integrates popCurrentScreenFromHistory on back navigation.            |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T12:26:52Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements a navigation history mechanism to allow users to easily go back to the "Posts & Pages" summary screen by recording and retrieving previous screens via sessionStorage. Key changes include:
- Introducing a new hook (useStatsNavigationHistory) with helper functions (recordCurrentScreen and popCurrentScreenFromHistory).
- Updating component wrappers in StatsSummary and StatsPostDetail to pass the lastScreen data for back navigation.
- Adjusting the navigation header to update the back link dynamically using the tracked history.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                                                 | Description                                                           |
| ---------------------------------------------------- | --------------------------------------------------------------------- |
| client/my-sites/stats/summary/index.jsx              | Wrapped StatsSummary with a navigation hook and replaced backLink usage. |
| client/my-sites/stats/stats-post-detail/index.jsx    | Wrapped StatsPostDetail and updated backLink properties to use lastScreen. |
| client/my-sites/stats/site.jsx                       | Added a useEffect to record the current screen on the Traffic page.    |
| client/my-sites/stats/hooks/use-stats-navigation.ts  | Implemented navigation history management with sessionStorage.        |
| client/components/navigation-header/navigation-header.tsx | Triggered removal of the current screen from history on back click.     |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T12:30:05Z
user: login: copilot-pull-request-reviewer[bot]
body: Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T12:35:49Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds navigational history support to various Stats screens, enabling users to return to the Posts & Pages summary after diving into detail views.

- Introduce `useStatsNavigationHistory`, `recordCurrentScreen`, and `popCurrentScreenFromHistory` for tracking navigation.
- Wrap `StatsSummary` and `StatsPostDetail` in wrapper components to supply `lastScreen` from the hook.
- Update back links in headers to use the tracked history and pop entries on back clicks.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                                                              | Description                                                     |
| ----------------------------------------------------------------- | --------------------------------------------------------------- |
| client/my-sites/stats/summary/index.jsx                           | Added history recording in lifecycle methods and a wrapper HOC. |
| client/my-sites/stats/stats-post-detail/index.jsx                 | Added history recording in lifecycle methods and a wrapper HOC. |
| client/my-sites/stats/stats-email-detail/index.jsx                | Imported navigation hooks but did not wire them into the component. |
| client/my-sites/stats/site.jsx                                    | Added `useEffect` to record the initial “traffic” screen.       |
| client/my-sites/stats/hooks/use-stats-navigation.ts               | New hook and utilities for navigation history management.       |
| client/components/navigation-header/navigation-header.tsx         | Pop history entry on back-button click.                         |
</details>



<details>
<summary>Comments suppressed due to low confidence (4)</summary>

**client/my-sites/stats/stats-email-detail/index.jsx:23**
* The imported navigation hooks are not used in this file. Either remove the unused imports or implement the same wrapper and lifecycle calls as in the Post Detail and Summary components.
```
import { useStatsNavigationHistory, recordCurrentScreen, } from 'calypso/my-sites/stats/hooks/use-stats-navigation';
```
**client/my-sites/stats/hooks/use-stats-navigation.ts:58**
* [nitpick] The state variable `lastScreen` holds an object with a `lastScreen` property, which is confusing. Consider renaming the state to something like `navigationState` to avoid shadowing the inner `lastScreen` field.
```
const [ lastScreen, setLastScreen ] = useState< { lastScreen: string; queryParams: QueryArgs; } >( {
```
**client/components/navigation-header/navigation-header.tsx:49**
* The back link anchor lacks an explicit `aria-label`. Consider adding `aria-label="Go back"` (or a localized equivalent) to improve screen-reader support.
```
<a
```
**client/my-sites/stats/hooks/use-stats-navigation.ts:1**
* The new navigation hook and its utilities lack unit tests. Consider adding tests to verify that `recordCurrentScreen`, `popCurrentScreenFromHistory`, and `useStatsNavigationHistory` correctly manage sessionStorage and compute back links.
```
import { useEffect, useMemo, useState } from '@wordpress/element';
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T12:37:35Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T12:45:09Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a new navigation history mechanism for the stats section to enable “go back” functionality on various screens such as Posts & Pages Summary and Post Detail pages. Key changes include:
- Adding a new hook (useStatsNavigationHistory) and helper functions (recordCurrentScreen and popCurrentScreenFromHistory) to manage navigation state.
- Wrapping key components (StatsSummary, StatsPostDetail) with higher‑order components to inject navigation history.
- Updating navigation header behavior to pop the current screen upon back button click.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                                            | Description                                                                                    |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| client/my-sites/stats/summary/index.jsx                         | Injects navigation history and updates backLinkProps in summary view.                           |
| client/my-sites/stats/stats-post-detail/index.jsx                 | Adds navigation tracking and wraps the component to include proper back link data.              |
| client/my-sites/stats/stats-email-detail/index.jsx               | Minor adjustment to align with updated navigation history usage.                               |
| client/my-sites/stats/site.jsx                                    | Incorporates navigation history record call within a useEffect to manage traffic screen state.   |
| client/my-sites/stats/hooks/use-stats-navigation.ts              | Implements the navigation history hook and helper functions with sessionStorage persistence.     |
| client/components/navigation-header/navigation-header.tsx         | Calls popCurrentScreenFromHistory on back button click to update navigation history accordingly. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**client/my-sites/stats/hooks/use-stats-navigation.ts:122**
* [nitpick] Consider adding minimal error logging or handling in the catch block of recordCurrentScreen so that potential JSON parsing issues or sessionStorage errors can be diagnosed in development.
```
try { ... } catch ( e ) {}
```
**client/my-sites/stats/site.jsx:906**
* [nitpick] The alias 'periodProp' could be renamed to simply 'period' for clarity if it does not conflict with any other variable names.
```
const { context, period: periodProp } = props;
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T13:05:36Z
user: login: dognose24
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T14:17:10Z
user: login: dognose24
body: I spun up the change on Odyssey Stats, which works mostly well. However, the go-back arrow does not show on the `Posts & Pages` summary when the path is `Traffic` > `Posts & Pages` summary > `Post Details`.

#### Calypso Stats
https://github.com/user-attachments/assets/d084839b-2263-49c2-8289-363f671d9a2e

#### Odyssey Stats
https://github.com/user-attachments/assets/a3a3d03a-5928-4fbf-a5b1-190f64a57a6c
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-18T14:49:26Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T08:37:50Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T08:38:01Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T08:42:46Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T08:44:45Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T14:48:36Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds a session‐based navigation history to stats screens so the “back” button returns users to the correct summary page with preserved filters.

- Introduces `useStatsNavigationHistory` hook plus `recordCurrentScreen`/`popCurrentScreenFromHistory` helpers for tracking.
- Updates StatsSummary, StatsPostDetail, and StatsSite to record screens on mount/update and wire back buttons to history.
- Refactors NavigationHeader to call `popCurrentScreenFromHistory` and render a `<button>` instead of an `<a>`.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                                                      | Description                                                         |
|---------------------------------------------------------------------------|---------------------------------------------------------------------|
| client/my-sites/stats/summary/index.jsx                                   | Integrated history hook, passed `lastScreen` into backLinkProps     |
| client/my-sites/stats/stats-post-detail/index.jsx                         | Added recording in lifecycle, replaced static nav with history data |
| client/my-sites/stats/site.jsx                                            | Fixed period destructuring, added initial screen record in useEffect|
| client/my-sites/stats/hooks/use-stats-navigation-history.ts              | New hook and helpers to store/populate navigation state             |
| client/components/navigation-header/navigation-header.tsx                | Switched `<a>` to `<button>`, wired `popCurrentScreenFromHistory`   |
| client/components/navigation-header/navigation-header.scss               | Added `cursor: pointer` for button                                  |
| client/components/navigation-header/stories/navigation-header.stories.tsx | Updated story callback to new signature                              |
</details>



<details>
<summary>Comments suppressed due to low confidence (7)</summary>

**client/components/navigation-header/navigation-header.tsx:47**
* Add `type="button"` to this `<button>` to prevent it from acting as a submit button if ever rendered inside a form.
```
<button className="calypso-navigation-header__back-link"
```
**client/components/navigation-header/navigation-header.tsx:47**
* Include an `aria-label` (e.g., `aria-label="Back"`) on the back button to ensure screen readers announce its purpose.
```
<button className="calypso-navigation-header__back-link"
```
**client/my-sites/stats/hooks/use-stats-navigation-history.ts:57**
* Add unit tests for `useStatsNavigationHistory`, `recordCurrentScreen`, and `popCurrentScreenFromHistory` to verify sessionStorage behavior and navigation stack manipulation.
```
export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
```
**client/my-sites/stats/site.jsx:903**
* [nitpick] Rename `periodProps` to something clearer like `periodData` or `periodInfo` to avoid confusion with the `period` property itself.
```
const { context, period: periodProps } = props;
```
**client/my-sites/stats/site.jsx:917**
* Missing import of `useEffect`. Add `import { useEffect } from 'react';` at the top to avoid a runtime ReferenceError.
```
useEffect( () => {
```
**client/my-sites/stats/hooks/use-stats-navigation-history.ts:89**
* Avoid swallowing errors silently. Consider logging exceptions (e.g., `console.error(e)`) or handling them to aid debugging.
```
} catch ( e ) {}
```
**client/my-sites/stats/hooks/use-stats-navigation-history.ts:175**
* Add a JSDoc comment above `popCurrentScreenFromHistory` describing its purpose and parameters, similar to the documentation for `recordCurrentScreen`.
```
export const popCurrentScreenFromHistory = (): void => {
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T14:59:13Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds navigation history management to Stats screens, enabling a consistent back-navigation experience across summary, detail, and site pages.

- Introduces a `useStatsNavigationHistory` hook plus `recordCurrentScreen` and `popCurrentScreenFromHistory` utilities that persist the navigation stack in `sessionStorage`.
- Updates `StatsSummary`, `StatsPostDetail`, and `StatsSite` components to record their screens and consume the last screen for back-link text and URL.
- Refactors `NavigationHeader` to use a `<button>` for the back action, invoking `popCurrentScreenFromHistory` and then routing via `page()`.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

<details>
<summary>Show a summary per file</summary>

| File                                                              | Description                                                       |
|-------------------------------------------------------------------|-------------------------------------------------------------------|
| client/my-sites/stats/summary/index.jsx                           | Imports history hook, records current module on mount/update, uses `lastScreen` for back link |
| client/my-sites/stats/stats-post-detail/index.jsx                 | Records post detail screen, wraps component to inject `lastScreen`, updates backLinkProps    |
| client/my-sites/stats/site.jsx                                    | Records traffic screen via `useEffect`, destructures `period` correctly                         |
| client/my-sites/stats/hooks/use-stats-navigation-history.ts       | New hook and utilities for tracking/popping navigation history in `sessionStorage`          |
| client/components/navigation-header/stories/navigation-header.stories.tsx | Adjusts story’s `onBackClick` to no-arg signature                          |
| client/components/navigation-header/navigation-header.tsx         | Switches back link from `<a>` to `<button>`, pops history before navigation                   |
| client/components/navigation-header/navigation-header.scss        | Adds `cursor: pointer` to back-button styling                                          |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:04:47Z
user: login: dognose24
body: Works well on Calypso and Odyssey Stats! Great work, @Nikschavan! 👍🏼 Left nitpicks that are not blockers. 🙂
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:41:39Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:48:44Z
user: login: Nikschavan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:49:18Z

Review comments:
diff_hunk: @@ -0,0 +1,161 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '{}' );
+			const lastItem = Array.isArray( navState ) ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem.lastScreen ) {
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Copilot
body: If navState contains fewer than 2 elements, then navState[navState.length - 2] will be undefined, which could lead to runtime errors when accessing lastItem.lastScreen. Consider adding a safe check to ensure lastItem is defined before using it.
```suggestion
			const lastItem = Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};

			// Make sure it's array and select last item
			if ( lastItem && lastItem.lastScreen ) {
```
created_at: 2025-05-16T12:26:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '{}' );
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Copilot
body: [nitpick] Consider using '[]' as the fallback value instead of '{}' to ensure that the parsed sessionStorage value is consistently an array, which aligns with later usage in the hook.
```suggestion
			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
```
created_at: 2025-05-16T12:30:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '{}' );
+			const lastItem = Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Copilot
body: [nitpick] Consider adding a comment to explain why the hook selects the second-to-last item from the navigation history (i.e. why navHistory[length - 2] is used) to clarify the intended back navigation behavior.
created_at: 2025-05-16T12:30:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -914,6 +915,11 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = props.context.query;
+		recordCurrentScreen( 'traffic', query, true );
+	}, [ props.period, props.context.query ] );
path: client/my-sites/stats/site.jsx
user: login: Copilot
body: [nitpick] Using `props.context.query` directly in the dependency array may trigger the effect on every render if the object identity changes. Consider destructuring specific query fields or memoizing the query to prevent unnecessary recordings.
```suggestion
		const { someField, anotherField } = props.context.query; // Replace with actual fields used
		recordCurrentScreen( 'traffic', { someField, anotherField }, true );
	}, [ props.period, props.context.query.someField, props.context.query.anotherField ] );
```
created_at: 2025-05-16T12:37:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,165 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+			// Select the second last item from the history stack as the back link.
+			// The last item in the stack if the current screen.
+			const lastItem =
+				Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem && lastItem.lastScreen ) {
+				setLastScreen( lastItem );
+			} else {
+				setLastScreen( {
+					lastScreen: defaultLastScreen,
+					queryParams: {},
+				} );
+			}
+		} catch ( e ) {}
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Copilot
body: Empty `catch` blocks silently swallow errors, which can make debugging difficult. Consider logging caught errors in development mode (e.g., `console.warn(e)`) or at least documenting why they're safely ignored.
```suggestion
		} catch ( e ) {
			// Log the error in development mode for debugging purposes.
			// Safe to ignore as fallback logic ensures default values are used.
			if (process.env.NODE_ENV === 'development') {
				console.warn('Error parsing navigation state from sessionStorage:', e);
			}
		}
```
created_at: 2025-05-16T12:37:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,165 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+			// Select the second last item from the history stack as the back link.
+			// The last item in the stack if the current screen.
+			const lastItem =
+				Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem && lastItem.lastScreen ) {
+				setLastScreen( lastItem );
+			} else {
+				setLastScreen( {
+					lastScreen: defaultLastScreen,
+					queryParams: {},
+				} );
+			}
+		} catch ( e ) {}
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Nikschavan
body: I am ignoring logging for now, but I have included try/catch blocks where there is JSON/session storage functionality
created_at: 2025-05-16T12:45:09Z
author_association: MEMBER
diff_hunk: @@ -52,6 +56,7 @@ class StatsPostDetail extends Component {
 		siteSlug: PropTypes.string,
 		showViewLink: PropTypes.bool,
 		previewUrl: PropTypes.string,
+		lastScreen: PropTypes.object,
path: client/my-sites/stats/stats-post-detail/index.jsx
user: login: Copilot
body: [nitpick] Consider providing a more detailed PropTypes shape for lastScreen (e.g. PropTypes.shape({ text: PropTypes.string, url: PropTypes.string })) to clarify the expected structure.
created_at: 2025-05-16T13:05:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,165 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+			// Select the second last item from the history stack as the back link.
+			// The last item in the stack if the current screen.
+			const lastItem =
+				Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem && lastItem.lastScreen ) {
+				setLastScreen( lastItem );
+			} else {
+				setLastScreen( {
+					lastScreen: defaultLastScreen,
+					queryParams: {},
+				} );
+			}
+		} catch ( e ) {}
+	}, [] );
+
+	const backLink = useMemo( () => {
+		if ( ! siteSlug ) {
+			return null;
+		}
+
+		const backLink = possibleBackLinks[ lastScreen.lastScreen ];
+
+		if ( ! backLink ) {
+			return null;
+		}
+
+		const queryParams = buildQueryString( getFilteredQueryParams( lastScreen.queryParams ) );
+
+		return backLink + siteSlug + ( queryParams ? '?' + queryParams : '' );
+	}, [ lastScreen, siteSlug ] );
+
+	return {
+		text: localizedTabNames[ lastScreen.lastScreen ] || '',
+		url: backLink,
+	};
+};
+
+/**
+ * Utility to record the current screen for back navigation
+ * @param {string} screen - Current screen identifier
+ * @param {Object} queryParams - Query parameters for the screen
+ * @param {boolean} reset - Whether to reset the navigation history
+ */
+export const recordCurrentScreen = (
+	screen: string,
+	queryParams: QueryArgs = {},
+	reset: boolean = false
+): void => {
+	try {
+		if ( ! screen || ! ( screen in localizedTabNames ) ) {
+			return;
+		}
+
+		const filteredQueryParams = getFilteredQueryParams( queryParams );
+		const currentEntry = {
+			lastScreen: screen,
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: dognose24
body: Is this actually about the current screen, rather than the _last screen_?
```suggestion
			screen,
```
created_at: 2025-05-16T13:25:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -43,6 +47,22 @@ const StatsStrings = statsStringsFactory();
 class StatsSummary extends Component {
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context } = this.props;
+		const { module } = context.params;
+		const { query } = context;
+
+		recordCurrentScreen( module, query );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( prevProps.statsQueryOptions !== this.props.statsQueryOptions ) {
path: client/my-sites/stats/summary/index.jsx
user: login: dognose24
body: If no property is specified for the comparison, we may need to perform a deep comparison for objects. For example, we could apply `lodash.isEqual` to those comparisons.

```suggestion
		if ( ! isEqual( prevProps.statsQueryOptions, this.props.statsQueryOptions ) ) {
```
created_at: 2025-05-16T14:07:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -93,6 +98,16 @@ class StatsPostDetail extends Component {
 
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context } = this.props;
+		recordCurrentScreen( 'postDetails', context.query );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( prevProps.statsQueryOptions !== this.props.statsQueryOptions ) {
path: client/my-sites/stats/stats-post-detail/index.jsx
user: login: dognose24
body: `statsQueryOptions` has not been passed to the `stats-post-detail` page, should we use `props.context` for comparison instead?
created_at: 2025-05-16T14:09:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -914,6 +916,11 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen( 'traffic', query, true );
+	}, [ periodProp, context.query ] );
path: client/my-sites/stats/site.jsx
user: login: dognose24
body: The `period` change is detected but not recorded in the history. It also seems reasonable to store the `period` property for a more accurate history, doesn't it?
created_at: 2025-05-16T14:16:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -93,6 +98,16 @@ class StatsPostDetail extends Component {
 
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context } = this.props;
+		recordCurrentScreen( 'postDetails', context.query );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( prevProps.statsQueryOptions !== this.props.statsQueryOptions ) {
path: client/my-sites/stats/stats-post-detail/index.jsx
user: login: Nikschavan
body: Done in 333c513af08e7e785fd8f7175b7b2f5f4ab0640c
created_at: 2025-05-19T08:37:49Z
author_association: MEMBER
diff_hunk: @@ -43,6 +47,22 @@ const StatsStrings = statsStringsFactory();
 class StatsSummary extends Component {
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context } = this.props;
+		const { module } = context.params;
+		const { query } = context;
+
+		recordCurrentScreen( module, query );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( prevProps.statsQueryOptions !== this.props.statsQueryOptions ) {
path: client/my-sites/stats/summary/index.jsx
user: login: Nikschavan
body: Thank you, done in 333c513af08e7e785fd8f7175b7b2f5f4ab0640c
created_at: 2025-05-19T08:38:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,165 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const possibleBackLinks: { [ key: string ]: string | null } = {
+	traffic: '/stats/day/',
+	insights: '/stats/insights/',
+	store: '/stats/store/',
+	ads: '/stats/ads/',
+	subscribers: '/stats/subscribers/',
+	posts: '/stats/day/posts/',
+	authors: '/stats/day/authors/',
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
+};
+
+const SUPPORTED_QUERY_PARAMS: string[] = [
+	'startDate',
+	'endDate',
+	'num',
+	'summarize',
+	'chartStart',
+	'chartEnd',
+	'shortcut',
+];
+
+const defaultLastScreen = 'traffic';
+
+const getFilteredQueryParams = ( queryParams: QueryArgs ): QueryArgs => {
+	return Object.fromEntries(
+		Object.entries( queryParams ).filter( ( [ key ] ) => SUPPORTED_QUERY_PARAMS.includes( key ) )
+	);
+};
+
+/**
+ * Hook for managing stats navigation state
+ * Supports reading/writing from sessionStorage and initializing from query params
+ * @returns { { text: string; url: string | null } }
+ */
+export const useStatsNavigationHistory = (): { text: string; url: string | null } => {
+	const [ lastScreen, setLastScreen ] = useState< {
+		lastScreen: string;
+		queryParams: QueryArgs;
+	} >( {
+		lastScreen: defaultLastScreen,
+		queryParams: {},
+	} );
+	const siteId = useSelector( getSelectedSiteId );
+	const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );
+
+	useEffect( () => {
+		try {
+			const navState = JSON.parse( sessionStorage.getItem( STORAGE_KEY ) || '[]' );
+
+			// Select the second last item from the history stack as the back link.
+			// The last item in the stack if the current screen.
+			const lastItem =
+				Array.isArray( navState ) && navState.length >= 2 ? navState[ navState.length - 2 ] : {};
+
+			// Make sure it's array and select last item
+			if ( lastItem && lastItem.lastScreen ) {
+				setLastScreen( lastItem );
+			} else {
+				setLastScreen( {
+					lastScreen: defaultLastScreen,
+					queryParams: {},
+				} );
+			}
+		} catch ( e ) {}
+	}, [] );
+
+	const backLink = useMemo( () => {
+		if ( ! siteSlug ) {
+			return null;
+		}
+
+		const backLink = possibleBackLinks[ lastScreen.lastScreen ];
+
+		if ( ! backLink ) {
+			return null;
+		}
+
+		const queryParams = buildQueryString( getFilteredQueryParams( lastScreen.queryParams ) );
+
+		return backLink + siteSlug + ( queryParams ? '?' + queryParams : '' );
+	}, [ lastScreen, siteSlug ] );
+
+	return {
+		text: localizedTabNames[ lastScreen.lastScreen ] || '',
+		url: backLink,
+	};
+};
+
+/**
+ * Utility to record the current screen for back navigation
+ * @param {string} screen - Current screen identifier
+ * @param {Object} queryParams - Query parameters for the screen
+ * @param {boolean} reset - Whether to reset the navigation history
+ */
+export const recordCurrentScreen = (
+	screen: string,
+	queryParams: QueryArgs = {},
+	reset: boolean = false
+): void => {
+	try {
+		if ( ! screen || ! ( screen in localizedTabNames ) ) {
+			return;
+		}
+
+		const filteredQueryParams = getFilteredQueryParams( queryParams );
+		const currentEntry = {
+			lastScreen: screen,
path: client/my-sites/stats/hooks/use-stats-navigation.ts
user: login: Nikschavan
body: Agreed, Done in faf6bfd91548028f32ea4c41ae7bec981f378650
created_at: 2025-05-19T08:42:46Z
author_association: MEMBER
diff_hunk: @@ -914,6 +916,11 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen( 'traffic', query, true );
+	}, [ periodProp, context.query ] );
path: client/my-sites/stats/site.jsx
user: login: Nikschavan
body: You are right about `periodProp`, and I have removed this. Re: period, I have noticed the page is redirected to the correct period `/month` / `/day` etc based on the query parameters so I decided to skip that logic from here. So right now the back link for traffic page is always `/day` with the query parameters and that redirects to correct path. Do you think this would be Ok or we should also store and redirect to the correct period too?
created_at: 2025-05-19T08:44:45Z
author_association: MEMBER
diff_hunk: @@ -914,6 +916,11 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen( 'traffic', query, true );
+	}, [ periodProp, context.query ] );
path: client/my-sites/stats/site.jsx
user: login: Nikschavan
body: I actually went ahead and pushed a fix to handle the period because

1. This will avoid URL redirect/change when the page is loading
2. This is not handled for other screens like `/subscribers`, so fixing this here will help maintain the period in URL for other screens 
created_at: 2025-05-19T14:48:35Z
author_association: MEMBER
diff_hunk: @@ -43,6 +47,28 @@ const StatsStrings = statsStringsFactory();
 class StatsSummary extends Component {
 	componentDidMount() {
 		window.scrollTo( 0, 0 );
+
+		const { context, period } = this.props;
+		const { module } = context.params;
+		const { query } = context;
+
+		recordCurrentScreen( module, {
+			queryParams: query,
+			period: period?.period,
+		} );
+	}
+
+	componentDidUpdate( prevProps ) {
+		if ( ! isEqual( prevProps.statsQueryOptions, this.props.statsQueryOptions ) ) {
path: client/my-sites/stats/summary/index.jsx
user: login: dognose24
body: ```suggestion
		if ( ! isEqual( prevProps.context.query, this.props.context.query ) ) {
```
Should we just compare `context.query` because they will be recorded?
created_at: 2025-05-20T14:53:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: query,
+				period: periodData?.period,
path: client/my-sites/stats/site.jsx
user: login: dognose24
body: ```suggestion
				period,
```
created_at: 2025-05-20T14:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: query,
path: client/my-sites/stats/site.jsx
user: login: dognose24
body: ```suggestion
				queryParams: context.query,
```
created_at: 2025-05-20T14:56:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
path: client/my-sites/stats/site.jsx
user: login: dognose24
body: ```suggestion

```
created_at: 2025-05-20T14:57:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,184 @@
+import { useEffect, useMemo, useState } from '@wordpress/element';
+import { buildQueryString } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { getSiteSlug } from 'calypso/state/sites/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+type QueryArgs = Record< string, string | null >;
+
+const STORAGE_KEY = 'jp-stats-navigation';
+
+const localizedTabNames: { [ key: string ]: string | null } = {
+	traffic: translate( 'Traffic' ),
+	insights: translate( 'Insights' ),
+	store: translate( 'Store' ),
+	ads: translate( 'Ads' ),
+	subscribers: translate( 'Subscribers' ),
+	posts: translate( 'Posts & pages' ),
+	authors: translate( 'Authors' ),
+	postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
path: client/my-sites/stats/hooks/use-stats-navigation-history.ts
user: login: Copilot
body: Avoid calling `translate()` at module top-level since translations may not be initialized or could change at runtime. Move `translate()` calls inside the hook (e.g., within `useMemo`) to ensure up-to-date localization.
```suggestion
const useLocalizedTabNames = (): { [ key: string ]: string | null } => {
	return useMemo(() => ({
		traffic: translate( 'Traffic' ),
		insights: translate( 'Insights' ),
		store: translate( 'Store' ),
		ads: translate( 'Ads' ),
		subscribers: translate( 'Subscribers' ),
		posts: translate( 'Posts & pages' ),
		authors: translate( 'Authors' ),
		postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
	}), []);
```
created_at: 2025-05-20T15:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: query,
+				period: periodData?.period,
+			},
+			true
+		);
path: client/my-sites/stats/site.jsx
user: login: Copilot
body: Using `reset: true` on every change to filters (context.query or period) will clear the navigation stack repeatedly. If the intent is to reset only on initial mount, use an empty dependency array or separate `useEffect` with `[]` so history isn’t unintentionally cleared on every filter update.
```suggestion
	// Run on initial mount to reset the navigation stack.
	useEffect( () => {
		recordCurrentScreen(
			'traffic',
			{
				queryParams: context.query,
				period: periodData?.period,
			},
			true
		);
	}, [] );

	// Track changes to context.query and periodData?.period without resetting the navigation stack.
	useEffect( () => {
		recordCurrentScreen(
			'traffic',
			{
				queryParams: context.query,
				period: periodData?.period,
			},
			false
		);
```
created_at: 2025-05-20T15:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -387,8 +419,8 @@ class StatsSummary extends Component {
 							className="stats__section-header modernized-header"
 							titleProps={ { title, titleLogo: null } }
 							backLinkProps={ {
-								url: backLink,
-								text: backLabel,
+								url: lastScreen.url,
path: client/my-sites/stats/summary/index.jsx
user: login: Copilot
body: `lastScreen.url` can be `null` if no previous entry exists. Consider providing a fallback URL (e.g., home or summary) to avoid rendering a back button that leads nowhere.
```suggestion
								url: lastScreen.url ?? '/stats/summary',
```
created_at: 2025-05-20T15:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,18 +44,21 @@ const NavigationHeader: React.FC< HeaderProps > = ( {
 	backLinkProps,
 	titleElement,
 	headElement = backLinkProps?.url && (
-		<a
+		<button
 			className="calypso-navigation-header__back-link"
-			href={ backLinkProps?.url }
-			onClick={ ( e ) => {
+			type="button"
path: client/components/navigation-header/navigation-header.tsx
user: login: Copilot
body: For better screen reader support, add an `aria-label` (e.g., `aria-label={ backLinkProps.text || translate('Back') }`) to the back button to clarify its purpose.
```suggestion
			type="button"
			aria-label={ backLinkProps?.text || translate( 'Back' ) }
```
created_at: 2025-05-20T15:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: query,
+				period: periodData?.period,
+			},
+			true
+		);
+	}, [ context.query, periodData?.period ] );
path: client/my-sites/stats/site.jsx
user: login: dognose24
body: ```suggestion
	}, [ context.query, period ] );
```
created_at: 2025-05-20T15:37:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -387,8 +419,8 @@ class StatsSummary extends Component {
 							className="stats__section-header modernized-header"
 							titleProps={ { title, titleLogo: null } }
 							backLinkProps={ {
-								url: backLink,
-								text: backLabel,
+								url: lastScreen.url,
path: client/my-sites/stats/summary/index.jsx
user: login: Nikschavan
body: null is expected, if URL is not available the back link is not displayed
created_at: 2025-05-21T10:48:43Z
author_association: MEMBER
diff_hunk: @@ -911,6 +914,18 @@ const StatsSite = ( props ) => {
 		[]
 	); // Track the last viewed tab.
 
+	useEffect( () => {
+		const query = context.query;
+		recordCurrentScreen(
+			'traffic',
+			{
+				queryParams: query,
+				period: periodData?.period,
+			},
+			true
+		);
path: client/my-sites/stats/site.jsx
user: login: Nikschavan
body: > Using reset: true on every change to filters (context.query or period) will clear the navigation stack repeatedly

This is expected behavior, when user reaches to this page the stack should reset
created_at: 2025-05-21T10:49:18Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-16T12:16:05Z
body: <details>
<summary>Calypso Live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-145127%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127">https://calypso.live?image=registry.a8c.com/calypso/app:build-145127</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Jetpack Cloud live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=jetpack">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-145127%26env%3Djetpack%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=jetpack">https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=jetpack</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Automattic for Agencies live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=a8c-for-agencies">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-145127%26env%3Da8c-for-agencies%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=a8c-for-agencies">https://calypso.live?image=registry.a8c.com/calypso/app:build-145127&env=a8c-for-agencies</a>
</td>
</tr>
</table>
</details>
<!--calypso-live-watermark:apr@v1-->
author_association: NONE
user: login: matticbot
created_at: 2025-05-16T12:16:18Z
body: <!-- icfy-watermark: c52822 -->
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

**App Entrypoints** (~38 bytes removed 📉 [gzipped])
<details>

```
name                    parsed_size           gzip_size
entry-main                   -162 B  (-0.0%)      -38 B  (-0.0%)
entry-subscriptions           -18 B  (-0.0%)       -3 B  (-0.0%)
entry-stepper                 -18 B  (-0.0%)       -3 B  (-0.0%)
entry-reauth-required         -18 B  (-0.0%)       -3 B  (-0.0%)
entry-login                   -18 B  (-0.0%)       -3 B  (-0.0%)
entry-domains-landing         -18 B  (-0.0%)       -3 B  (-0.0%)
entry-dashboard-dotcom        -18 B  (-0.0%)       -3 B  (-0.0%)
entry-dashboard-a4a           -18 B  (-0.0%)       -3 B  (-0.0%)
entry-browsehappy             -18 B  (-0.0%)       -3 B  (-0.0%)
```

Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.
</details>

**Sections** (~473 bytes removed 📉 [gzipped])
<details>

```
name                parsed_size           gzip_size
stats                   +2585 B  (+0.2%)    +1566 B  (+0.5%)
google-my-business      +2224 B  (+0.4%)     +969 B  (+0.6%)
marketing                -196 B  (-0.0%)    -1007 B  (-0.4%)
jetpack-social           -196 B  (-0.0%)    -1007 B  (-0.5%)
woocommerce               +90 B  (+0.0%)      +18 B  (+0.0%)
```

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.
</details>

**Async-loaded Components** (~1746 bytes added 📈 [gzipped])
<details>

```
name                                                 parsed_size           gzip_size
async-load-store-app-store-stats                         +2420 B  (+0.9%)    +1511 B  (+1.9%)
async-load-calypso-my-sites-stats-summary                 +493 B  (+1.0%)     +122 B  (+0.9%)
async-load-calypso-my-sites-stats-stats-post-detail       +402 B  (+1.2%)     +113 B  (+1.2%)
```

React components that are loaded lazily, when a certain part of UI is displayed for the first time.
</details>

**Legend**
<details>
<summary>What is parsed and gzip size?</summary>

**Parsed Size:** Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
**Gzip Size:** Compressed size of the JS and CSS files. This much data needs to be downloaded over network.
</details>

Generated by performance advisor bot at [iscalypsofastyet.com](http://iscalypsofastyet.com).
author_association: CONTRIBUTOR
user: login: matticbot
created_at: 2025-05-16T12:36:22Z
body: **This PR modifies the release build for the following Calypso Apps:**

_For info about this notification, see here: PCYsg-OT6-p2_

* help-center
* notifications
* odyssey-stats
* wpcom-block-editor

To test WordPress.com changes, run `install-plugin.sh $pluginSlug stats-69-navigation-allow-go-back-to-posts-pages-summary-page` on your sandbox.
<!--calypso-app-artifacts-watermark:apr@v1-->
author_association: CONTRIBUTOR