import { Navigate, useLocation } from 'react-router'; import { usePermissions } from 'hooks/usePermissions'; // A screen exists only if this server has the thing behind it and this account may reach it. Otherwise the // path is treated exactly as an unknown one: redirect home, same as App.tsx's `path="*"`. // // ── Why a redirect and not an explanation ── // // The first version of this rendered a panel saying "Music is not installed" with a link to the app store, // on the reasoning that a redirect erases what you asked for. That was wrong, and the owner's correction is // the better principle: a naked platform should not know about a sidecar it does not have. Explaining the // absence of Music is the app describing a feature that, as far as this server is concerned, does not exist — // and it leaks the whole catalogue of what could be installed to every member who types a URL. // // So "no such page" is the honest answer, and it is the same answer for a member without a grant, for an // owner whose sidecar is not installed, and for a typo. One behaviour, nothing disclosed. // // This is still a courtesy rather than the lock — every one of these routes is refused server-side too. What // it stops is the app offering a door it will then slam. // // ── What this is NOT ── // // Routes are still declared in App.tsx for every screen, and this hides the ones that should not resolve. The // end state the owner described is different and better: routes REGISTERED from the manifests of installed // sidecars, so an uninstalled feature has no route to hide. The manifests already exist (`plugins`, carrying // `rootRoute` and `routes`) and the dock is already built from them; the router is not, yet. export function RouteGate({ children }: { children?: React.ReactNode }) { const { pathname } = useLocation(); const { denialReason } = usePermissions(); // `replace`, so Back does not bounce between the denied path and home. if (denialReason(pathname)) return ; return <>{children}; }