Follow-up from PR #562 review (thread), split out so it doesn't block graduating the MULTI_SITE_ENABLED flag.
Problem
PageHeader is mounted by AppLayout on all normal authenticated pages. After the multi-site flag removal, its fetchSites() effect runs unconditionally. ListSites is server-gated on org-scoped site:read, so a user lacking that permission (e.g. the built-in FIELD_TECH role) gets PermissionDenied, and the top bar renders SitePicker's "Sites unavailable" retry affordance everywhere. FleetLayout already hides site-backed tabs for the same case, so the header is inconsistent with the rest of the UI.
Proposed fix
Gate the fetch and SitePicker render on useHasPermission("site:read"), and initialize sites to [] when the user is blocked, so non-site readers keep a clean header.
Notes
FleetLayout already does the analogous gating (canReadSites, with a [] initial state when blocked) — mirror that pattern.
- File:
client/src/protoFleet/components/PageHeader/PageHeader.tsx.
Follow-up from PR #562 review (thread), split out so it doesn't block graduating the
MULTI_SITE_ENABLEDflag.Problem
PageHeaderis mounted byAppLayouton all normal authenticated pages. After the multi-site flag removal, itsfetchSites()effect runs unconditionally.ListSitesis server-gated on org-scopedsite:read, so a user lacking that permission (e.g. the built-inFIELD_TECHrole) getsPermissionDenied, and the top bar rendersSitePicker's "Sites unavailable" retry affordance everywhere.FleetLayoutalready hides site-backed tabs for the same case, so the header is inconsistent with the rest of the UI.Proposed fix
Gate the fetch and
SitePickerrender onuseHasPermission("site:read"), and initializesitesto[]when the user is blocked, so non-site readers keep a clean header.Notes
FleetLayoutalready does the analogous gating (canReadSites, with a[]initial state when blocked) — mirror that pattern.client/src/protoFleet/components/PageHeader/PageHeader.tsx.