Skip to content

Stats post detail: show email tabs based on actual email stats - #113843

Merged
dognose24 merged 6 commits into
trunkfrom
fix/stats-456-post-detail-email-tabs
Aug 31, 2026
Merged

Stats post detail: show email tabs based on actual email stats#113843
dognose24 merged 6 commits into
trunkfrom
fix/stats-456-post-detail-email-tabs

Conversation

@dognose24

@dognose24 dognose24 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes STATS-456

Proposed Changes

  • Add usePostEmailStatsAvailabilityQuery, a small react-query hook that reads /sites/:site/stats/opens/emails/:post/rate and reports whether the post has any email sends or opens.
  • The post detail page now shows the Post traffic / Email opens / Email clicks tabs when that query says the post has email stats, instead of guessing from the _jetpack_dont_email_post_to_subs post meta and a 2023-05-30 publish-date cutoff.
  • Remove the now-unused dont_email_post_to_subs plumbing from getPost() and the highlights section type.

Why are these changes being made?

The post detail page and the email detail page share the same tab strip but decided differently whether to render it: the email page always did, while the post page relied on post metadata. A post that carries _jetpack_dont_email_post_to_subs but still has email stats (the case in STATS-456) showed all three tabs on the email page and none on the post page, so clicking Post traffic made the tabs disappear.

Basing the decision on the email stats themselves keeps both pages consistent and also covers the old publish-date cutoff: posts from before newsletter stats existed simply have no sends and get no tabs. This supersedes the metadata check from #97739, which had the same intent (no tabs for posts that were never sent as an email) but relied on a flag that does not always match what was actually sent. In the reported case the email went out at publish time and the meta was only flipped to true by a later edit, so the flag reflects the editor's last-saved state rather than what was sent.

Testing Instructions

Finding a post that reproduces the bug. The tabs only went missing for posts that have email stats and carry the _jetpack_dont_email_post_to_subs meta. A newsletter sent through the normal flow does not carry it, so either:

  • use the post from STATS-456 (an Automattic-owned Simple site, so a11ns can open its Stats), or
  • build one on a self-hosted test site with newsletters enabled and at least one subscriber: publish a post with "Post and email", wait for it to show up under Emails on the Traffic page, then set the meta with WP-CLI (it is protected meta, so the Custom Fields panel cannot add it): wp post meta update <post_id> _jetpack_dont_email_post_to_subs 1.

Steps:

  1. Open the Traffic page and click the post above in Posts & Pages.
  2. Before: no email tabs. After: Post traffic / Email opens / Email clicks tabs are shown.
  3. Open the same post from the Emails module and click Post traffic: the tabs are shown again once the post detail page has loaded (the strip briefly disappears while the page switches; that also happens on trunk and is not addressed here).
  4. Regression checks, unchanged before and after:
    • a normal newsletter post (no custom field): tabs shown;
    • a post that was never sent as an email, e.g. one published before 2023-05-30 or with "Post only": no email tabs;
    • the site's home page entry (post ID 0): no tabs.

Also worth checking on a self-hosted Jetpack site with newsletters enabled, since the rate endpoint goes through the Jetpack API there.

Screenshots

Before After
截圖 2026-08-27 下午1 03 17 截圖 2026-08-27 下午1 03 23

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • For UI changes, have you tested the affected components in dark mode?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • 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)?

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

Async-loaded Components (~108 bytes added 📈 [gzipped])

Details
Name Parsed Gzip
async-load-calypso-my-sites-stats-stats-post-detail +391 B +1.2% +108 B +1.0%

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

Legend

What is parsed and gzip size?

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.

The post detail page hid the Email opens / Email clicks tabs based on the
_jetpack_dont_email_post_to_subs post meta and a publish-date cutoff, while
the email detail page always shows them. Posts that have email stats but
carry that meta lost the tabs when navigating from the email page to Post
traffic. Gate on the email rate endpoint instead, so both pages agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dognose24
dognose24 force-pushed the fix/stats-456-post-detail-email-tabs branch from 0fd453b to f70c23a Compare August 26, 2026 15:04
@dognose24
dognose24 requested a review from kangzj August 27, 2026 05:02
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Aug 27, 2026
@dognose24
dognose24 requested review from a team and gabrielcaires and a lite review from Copilot August 27, 2026 05:02
@dognose24 dognose24 self-assigned this Aug 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Calypso Stats post detail page to decide whether to show the Post traffic / Email opens / Email clicks tab strip based on actual email stats availability, aligning behavior with the email detail page and avoiding incorrect heuristics based on post metadata and publish date cutoffs.

Changes:

  • Add a new React Query hook (usePostEmailStatsAvailabilityQuery) that checks the email “rate” stats endpoint to determine if a post has any email sends/opens.
  • Update the post detail page to show email tabs only when that hook reports email stats exist (and when subscriptions + email stats are supported).
  • Remove the now-unused dont_email_post_to_subs plumbing from the post object shape used by the highlights section.

Reviewed changes

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

File Description
client/my-sites/stats/stats-post-detail/index.jsx Switch tab availability logic to rely on hasEmailStats from the new query hook, and remove metadata/date heuristics.
client/my-sites/stats/post-detail-highlights-section/index.tsx Remove the dont_email_post_to_subs field from the Post type since it’s no longer provided/used.
client/my-sites/stats/hooks/use-post-email-stats-availability-query.ts Introduce a small React Query hook to fetch email rate stats and derive a boolean “has email stats” signal.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread client/my-sites/stats/stats-post-detail/index.jsx Outdated
@dognose24
dognose24 marked this pull request as ready for review August 27, 2026 05:12
@dognose24
dognose24 requested a review from chihsuan August 27, 2026 05:14
dognose24 and others added 2 commits August 27, 2026 13:18
…n show

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A newsletter that is still being sent can briefly report zero sends, so
only keep a positive answer fresh; a negative one is refetched on the
next mount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Looks like one of the E2E tests has failed.

You can fix them following these steps:

  1. Check out this branch locally:
    gh pr checkout 113843
  2. Start Claude Code in the repo:
    claude
  3. Run the /fix-e2e-tests skill, passing this PR number:
    /fix-e2e-tests 113843
    

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! @dognose24 Basing the tabs on the actual email stats reads much better than the metadata guess. 👍

I left a few inline comments, mostly around what happens when the /rate request fails.

Also, would a small unit test for hasEmailStats be worth adding? Thanks!

return supportsEmailStats && subscriptionsEnabled;
} );

const { data: hasEmailStats = false } = usePostEmailStatsAvailabilityQuery(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we surface isError here too? Reading only data makes a failed /rate request look identical to a post with no email stats, so the tabs quietly disappear. And with retryOnMount: false from the defaults, they stay hidden until a full page reload. Would failing open be safer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6760a25: the hook now sets retryOnMount: true (the shared defaults pin an errored query until a full reload), so navigating back retries instead of leaving the tabs hidden. The error still reads as unavailable rather than surfacing UI, which fails toward the pre-existing behavior of the page.

isJetpackSite( state, siteId, { treatAtomicAsJetpackSite: false } )
);

const canHaveEmailStats = useSelector( ( state ) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might not fully understand the wiring here — doesn't connect wrap this component, so supportsEmailStats, isSimple and isSubscriptionsModuleActive already arrive as props? Re-selecting them puts the same rule in three places in this file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, connect wraps this component, I misread the composition. Fixed in 6760a25: the wrapper now reads the checks from props and computes the whole rule (canHaveEmailStats, the postId guard, and the query result) in one place, passing a single isEmailTabsAvailable down; the render-side recomputation is gone.

}

function hasEmailStats( data?: EmailRateResponse ) {
return ( data?.total_sends ?? 0 ) > 0 || ( data?.total_opens ?? 0 ) > 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious — is 0 always a real zero on this endpoint? I found a note on #110760 where the API side said unique_clicks: 0 means data unavailable. I don't know whether total_sends behaves the same way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good instinct, and it is documented: total_sends is not always a real zero. STATS-446 records the same send reporting total_sends 0 on one endpoint and 1 on another for legacy sends. That is why this check also accepts total_opens > 0, and open tracking covers every send since the unique-tracking era. For the remaining untracked-era posts the behavior matches the date-cutoff heuristic this PR replaces (those tabs were already hidden by the 2023-05-30 guard), so a false negative here fails toward the existing state, and the email details page stays reachable from the Emails module.

) {
return useQuery( {
...getDefaultQueryParams(),
queryKey: [ 'stats', 'emails', 'rate', siteId, postId ],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should the key carry opens too? The URL pins the stat type but the key doesn't, so a future clicks-rate query with the same shape would share this cache entry.

Suggested change
queryKey: [ 'stats', 'emails', 'rate', siteId, postId ],
queryKey: [ 'stats', 'emails', 'opens', 'rate', siteId, postId ],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted in 6760a25, thanks.

The whole email-tabs rule now lives in the wrapper, which already
receives the environment checks as connect props; a failed availability
request retries on remount instead of pinning the tabs hidden until a
full reload; and the query key carries the stat type it pins in the URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dognose24
dognose24 requested review from chihsuan and a lite review from Copilot August 27, 2026 15:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

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

@kangzj kangzj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dognose24, thanks for taking this one on 🙂 Basing the tabs on the actual email stats rather than the meta flag makes total sense to me, and it's nice that it takes the 2023-05-30 cutoff out at the same time.

The feature itself works well for me in the browser - but I'm requesting changes on one thing, sorry.

Blocker

  1. canHaveEmailStats can evaluate to null rather than false, and react-query v5 hard-throws on a non-boolean enabled. Because it throws during render the whole Stats screen goes to a white page, not just the tabs. Reproduced live on this branch, and trunk is fine with the same state - details inline.

Non-blocking

  • No test for the new hook. hasEmailStats is nice pure logic - null / 0 / positive - and there's already a client/my-sites/stats/hooks/test folder, so it'd be pretty cheap to add one. Up to you.
  • Testing step 3 says the tabs stay when you click Post traffic from the Emails page. They don't quite - I measured a ~760ms window where the strip is gone. But trunk does the same thing (~640ms), so it's pre-existing and not yours to fix, I'm just flagging it so the note doesn't mislead whoever tests next.
  • I could NOT check Odyssey - I didn't build it into a docker env. That's the one I'd most like a second pair of eyes on for the blocker above, because isSimple is false in wp-admin, so the whole thing then rests on active_modules being present in the jetpack/v4 /site payload. Are you able to give it a spin there please?

Testing performed

Local Calypso dev env on this branch, logged in as me, mostly against en.blog.wordpress.com since it has real newsletter stats:

  • post with email stats (76414 - 68,036 sends, 9,109 opens) → Post traffic / Email opens / Email clicks shown ✅
  • post with no email stats (227, published 2005) → no tabs ✅
  • home page entry (post id 0) → no tabs ✅
  • Emails page → click Post traffic → tabs there afterwards ✅
  • normal loads of the post detail page on Simple, Atomic and Jetpack sites → all render fine ✅
  • yarn typecheck-client → no new errors in the touched files ✅
  • crash repro, this branch vs trunk → ❌ this branch white screens, trunk doesn't

I poked the endpoint directly too, couple of things worth knowing:

  • it returns real numbers (total_sends: 68036), not strings, so hasEmailStats is fine as it is
  • a post that was never emailed comes back 200 with all-null counters, so no error storm there - good
  • a post id that doesn't exist comes back 500 though, so a stale post id in the URL costs two failed requests with retry: 1. Very minor

Evidence

Tabs working on this branch (post 76414) White screen after the enabled throw
tabs shown on post detail white screen

Console at the moment it goes white:

Uncaught Error: Expected enabled to be a boolean or a callback that returns a boolean

Thanks again - happy to re-review as soon as the !! is in 👍

// `connect` wraps this component, so the environment checks arrive as props;
// the whole email-tabs rule lives here rather than being re-derived in render.
const { supportsEmailStats, isSimple, isSubscriptionsModuleActive, postId } = props;
const canHaveEmailStats = supportsEmailStats && ( isSimple || isSubscriptionsModuleActive );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isJetpackModuleActive returns null (not false) when it can't tell - the jetpack modules state hasn't loaded yet and active_modules isn't around either - so this line can come out as null, and that goes straight into the hook's enabled. react-query v5 hard-throws on a non-boolean enabled, and since it throws during render it takes the whole Stats screen down to a white page rather than just dropping the tabs.

I reproduced it on this branch by putting the store into the state those selectors are documented to return (site missing from state.sites.items while the post detail route is mounted): white page, Uncaught Error: Expected enabled to be a boolean or a callback that returns a boolean. trunk survives the exact same state.

To be fair I couldn't pin down a natural navigation that gets there in Calypso - Simple sites short-circuit on isSimple, and for Jetpack/Atomic the /me/sites payload happens to carry active_modules - so right now it's only that which is holding it up. Since it's one character I'd rather just make it safe:

Suggested change
const canHaveEmailStats = supportsEmailStats && ( isSimple || isSubscriptionsModuleActive );
const canHaveEmailStats = !! supportsEmailStats && !! ( isSimple || isSubscriptionsModuleActive );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Both coerced in bd81280.

...getDefaultQueryParams(),
queryKey: [ 'stats', 'emails', 'opens', 'rate', siteId, postId ],
queryFn: () => queryEmailRate( siteId as number, postId ),
enabled: enabled && !! siteId && postId > 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing from the other side - this is a shared hook now, so it probably shouldn't trust the caller to hand it a real boolean:

Suggested change
enabled: enabled && !! siteId && postId > 0,
enabled: !! enabled && !! siteId && postId > 0,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in bd81280.

enabled: enabled && !! siteId && postId > 0,
// A "no email stats" answer can be transient while a newsletter is still being sent,
// so only a positive result is kept for a while.
staleTime: ( query ) => ( hasEmailStats( query.state.data ) ? 1000 * 60 * 5 : 0 ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor one - this resolves to 0 for the "no email stats" answer, and with refetchOnMount on by default that's a fresh request every time someone opens a post detail page, which is the common case. I checked the endpoint and a never-emailed post does return 200 with all-null counters, so at least there's no retry storm. Would the 30s from the shared defaults be enough here?

Suggested change
staleTime: ( query ) => ( hasEmailStats( query.state.data ) ? 1000 * 60 * 5 : 0 ),
staleTime: ( query ) => ( hasEmailStats( query.state.data ) ? 1000 * 60 * 5 : 1000 * 30 ),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in bd81280: 30s for the negative result.

…e result briefly, add hook test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5G7Fg8tpLg8Qe9EaSs79t
@dognose24

dognose24 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. Addressed in bd81280:

  • !! on both canHaveEmailStats and the hook's enabled, so a null from isJetpackModuleActive can no longer reach react-query.
  • Negative result now cached for 30s instead of 0.
  • Added use-post-email-stats-availability-query.test.ts covering hasEmailStats for undefined / null / 0 / positive.
  • Reworded testing step 3 to note the brief gap when switching pages is pre-existing.

Odyssey check, on a local Jetpack docker site (wp-admin → Stats) with this branch's bundle built into stats-admin/dist:

  • jetpack/v4/site payload carries options.active_modules (stats, subscriptions present), so isSubscriptionsModuleActive resolves to a real boolean there; with the !! guards in place a missing array would now only hide the tabs.
  • Post with sends (/stats-app/.../stats/opens/emails/174/ratetotal_sends: 2): Post traffic / Email opens / Email clicks shown.
  • Post with sends but no opens (91 → total_sends: 2, total_opens: 0): tabs shown.
  • Home page entry (post 0): no tabs, no /rate request.
  • No page errors on any of them; the /rate call goes through the stats-app proxy fine.

@dognose24
dognose24 requested a review from kangzj August 28, 2026 07:50

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! @dognose24 This reads much cleaner now. 👍

I left a few small inline notes. The only one I feel is worth resolving before merge is the failed-request case, since retryOnMount only helps after a remount.

Just curious: the email tab already fetches the same /rate URL through emailStatsAlltime. Is a second cache for it worth it here? Thanks!

Comment on lines +337 to +341
const { data: hasEmailStats = false } = usePostEmailStatsAvailabilityQuery(
siteId,
postId,
canHaveEmailStats
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we read isError here too? A failed /rate request looks identical to a post that was never emailed. And retryOnMount only fires on remount, so the tabs may stay hidden for that whole visit.

Suggested change
const { data: hasEmailStats = false } = usePostEmailStatsAvailabilityQuery(
siteId,
postId,
canHaveEmailStats
);
const { data, isError } = usePostEmailStatsAvailabilityQuery(
siteId,
postId,
canHaveEmailStats
);
const hasEmailStats = data ?? isError;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, applied in 2f79b81: hasEmailStats = data ?? isError, so a failed request shows the tabs rather than hiding them.

// A failed request reads the same as "no email stats" and hides the tabs, so
// let a remount retry instead of pinning the error until a full reload
// (the shared defaults set retryOnMount: false).
retryOnMount: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should this opt out of persistence? shouldDehydrateQuery defaults to true, so a false answer gets written to localStorage and rendered on the next load before the 30s refetch corrects it.

Suggested change
retryOnMount: true,
retryOnMount: true,
meta: { persist: false },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 2f79b81.

return useQuery( {
...getDefaultQueryParams(),
queryKey: [ 'stats', 'emails', 'opens', 'rate', siteId, postId ],
queryFn: () => queryEmailRate( siteId as number, postId ),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could queryEmailRate take number | null so this cast can go? The template literal is unchanged, and the non-null claim currently rests on enabled, which TypeScript can't see.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 2f79b81.

@dognose24

Copy link
Copy Markdown
Contributor Author

Thanks! All three applied in 2f79b81.

On the emailStatsAlltime overlap: that one lives in Redux (client/state/stats/emails/actions.js → wpcom.js site.emailStatsAlltime), only runs once the email tab mounts, and fetches rate + client + device + country together. Reusing it for the tab decision would mean four requests up front for a single boolean, plus a different store with no error or negative caching. This hook hits /rate alone, so it's the cheaper path; the two don't share a cache today, but the extra request is one small GET per post detail visit.

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dognose24 LGTM! 🚀

@kangzj kangzj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 🚀 🚀

@dognose24

Copy link
Copy Markdown
Contributor Author

Follow-up: #113931 (STATS-464) addresses the tab-strip loading flash discussed above — route-level prefetch of the availability query plus cache seeding on the Emails → Post traffic navigation. It's based on this branch and will retarget to trunk once this merges.

@dognose24
dognose24 merged commit 558851e into trunk Aug 31, 2026
19 checks passed
@dognose24
dognose24 deleted the fix/stats-456-post-detail-email-tabs branch August 31, 2026 04:00
@github-actions github-actions Bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants