Skip to content

Dashboard: only use sites the user is a member of for the omnibar and recent sites - #112479

Merged
arthur791004 merged 1 commit into
trunkfrom
fix/omnibar-recent-sites-membership
Jul 10, 2026
Merged

Dashboard: only use sites the user is a member of for the omnibar and recent sites#112479
arthur791004 merged 1 commit into
trunkfrom
fix/omnibar-recent-sites-membership

Conversation

@arthur791004

@arthur791004 arthur791004 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Proposed Changes

  • When initializing the omnibar's current site, resolve it from the route site, the origin_site_id URL param, then a fallback (last omnibar site → most recent → primary blog) — but only accept a candidate the user is actually a member of.
  • Record that resolved site as the most recent site, so only sites the user belongs to are persisted to the recentSites preference.
  • Gate initialization on each query's loading state (not on data presence), so an inaccessible or errored site id settles and can't block the omnibar from initializing forever.
  • Interim omnibar (backport masterbar) now reflects the current site rather than always the primary blog.

Why are these changes being made?

The dashboard set and recorded whatever site id it resolved — including one taken straight from the untrusted origin_site_id URL param — with no check that the site belongs to the user. Classic Calypso filtered recent sites against the user's own sites; the dashboard path dropped that guard, so a crafted or stale id could be persisted and then fail to load. Membership is now the gate (any role, not just admin), matching the classic behavior.

Testing Instructions

  • In the dashboard, navigate between several of your sites and confirm the omnibar and the most-recent-site list update as before.
  • Load a dashboard URL with ?origin_site_id=<id of a site you do not belong to> and confirm it is not set as the omnibar site or added to recents, the param is stripped from the URL, and the omnibar still initializes from a valid fallback.
  • Load a dashboard URL with an inaccessible or garbage origin_site_id and confirm the omnibar still initializes (no hang).
  • Confirm no TypeScript/React console errors.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • 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?
  • Have you used memoizing on expensive computations?
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
  • 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)?

Considerations

Membership is detected via the presence of the site's capabilities property from GET /sites/:id (a local isMemberOfSite helper). This includes all member roles — broader than an admin-only manage_options check — and reliably excludes private sites the user cannot access, since that lookup errors. One edge remains unverified: whether the endpoint returns capabilities for a public site the user has no role on. If it does, such a site could still be recorded; impact is limited to the user's own preference.

@matticbot

matticbot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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

App Entrypoints (~71 bytes added 📈 [gzipped])

Details
name                    parsed_size           gzip_size
entry-dashboard-dotcom       +240 B  (+0.0%)      +71 B  (+0.0%)
entry-dashboard-ciab         +240 B  (+0.0%)      +71 B  (+0.0%)
entry-dashboard-a4a          +240 B  (+0.0%)      +71 B  (+0.0%)

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

Sections (~71 bytes added 📈 [gzipped])

Details
name                parsed_size           gzip_size
staging-site             +240 B  (+0.0%)      +71 B  (+0.0%)
sites-dashboard          +240 B  (+0.0%)      +71 B  (+0.0%)
site-settings            +240 B  (+0.0%)      +71 B  (+0.0%)
site-performance         +240 B  (+0.0%)      +71 B  (+0.0%)
site-monitoring          +240 B  (+0.0%)      +71 B  (+0.0%)
site-logs                +240 B  (+0.0%)      +71 B  (+0.0%)
plans                    +240 B  (+0.0%)      +71 B  (+0.0%)
overview                 +240 B  (+0.0%)      +71 B  (+0.0%)
hosting                  +240 B  (+0.0%)      +71 B  (+0.0%)
github-deployments       +240 B  (+0.0%)      +71 B  (+0.0%)
domains                  +240 B  (+0.0%)      +71 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.

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

Details
name                                                                   parsed_size           gzip_size
async-load-calypso-my-sites-customer-home-celebrate-site-launch-modal       +240 B  (+0.0%)      +71 B  (+0.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.

Generated by performance advisor bot at iscalypsofastyet.com.

@arthur791004 arthur791004 self-assigned this Jul 9, 2026
@arthur791004
arthur791004 force-pushed the fix/omnibar-recent-sites-membership branch from 0f4da45 to 6e408ca Compare July 9, 2026 14:10
@arthur791004 arthur791004 changed the title Dashboard: only record sites the user belongs to as recent/omnibar site Dashboard: only use sites the user is a member of for the omnibar and recent sites Jul 9, 2026
@arthur791004
arthur791004 marked this pull request as ready for review July 9, 2026 14:16
@arthur791004
arthur791004 requested a review from a team as a code owner July 9, 2026 14:16
@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 Jul 9, 2026
@arthur791004
arthur791004 force-pushed the fix/omnibar-recent-sites-membership branch from 6e408ca to f270ec5 Compare July 9, 2026 14:26
Comment thread client/dashboard/app/omnibar/site.ts
@arthur791004
arthur791004 force-pushed the fix/omnibar-recent-sites-membership branch from f270ec5 to 83eb8d3 Compare July 9, 2026 14:40
const omnibarSite = [ routeSite, originSite, fallbackSite ].find(
( site ) => site && isMemberOfSite( site )
);
const omnibarSiteId = omnibarSite?.ID ?? user?.primary_blog;

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.

Is it possible that the user's primary blog is a blog they no longer have access to? Does the user need to be validated as a member as well?

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.

The primary blog is configured through Preferences > Account Defaults, where you can select any of your sites. However, if you choose a site that you don’t own as your primary blog, you may be removed from that site. That said, the likelihood of this happening is very low.

@arthur791004
arthur791004 force-pushed the fix/omnibar-recent-sites-membership branch from 83eb8d3 to bbaf07f Compare July 10, 2026 01:47
@StevenDufresne

StevenDufresne commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

I'm not against running the two queries simultaneously, but I'm always hesitant to do so when we need to resolve a winner.

Membership feels like an edge case that shouldn't introduce delays for regular users.

It may be better to just catch and move on. Here's a quick prototype pr: #112495 (Disclaimer, AI generated)

Thoughts?

onToggleNotifications,
}: Props ) {
const user = userProp ?? emptyUser;
const siteId = user.primary_blog ?? null;

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.

I'm surprised then how come it's working until now ??

import { AUTH_QUERY_KEY } from '../auth';
import type { Site, User } from '@automattic/api-core';

function isMemberOfSite( site: Site ) {

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.

Nit: maybe add this as canViewSite() in client/dashboard/sites/features.ts so that it's paired nicely with canManageSite() in that 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.

but can't any one view the site 🤔

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.

Hmm... that's right... maybe isMemberOfSite() is clearer after all 😅

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.

yes, but it doesn't seem to match functions in this file... so I just inlined it 🙈

Comment thread client/dashboard/app/omnibar/site.ts Outdated
Validate site membership before persisting a site as the most recent site
or setting it as the omnibar site, so a crafted or inaccessible
`origin_site_id` can no longer be recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@fushar fushar 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.

Tested the cases. Works, thanks!

@arthur791004
arthur791004 added this pull request to the merge queue Jul 10, 2026
Merged via the queue into trunk with commit 71c7c80 Jul 10, 2026
18 checks passed
@arthur791004
arthur791004 deleted the fix/omnibar-recent-sites-membership branch July 10, 2026 11:39
@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 Jul 10, 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.

4 participants