(null);
const [showCreateDialog, setShowCreateDialog] = useState(false);
@@ -75,6 +90,13 @@ export function PermissionsPage() {
if (permLoading || rolesLoading || settingsLoading) return ;
+ // `settings` requires dashboard.settings.manage, which the nav gate
+ // (dashboard.roles.view) does not guarantee — e.g. the built-in Viewer
+ // preset. When the fetch 403s, `settings` is undefined; don't guess at
+ // requirePermissions in that case, since the "system disabled" banner is a
+ // security claim about the guild and rendering it here would be false for
+ // guilds where it's actually enabled.
+ const settingsLoaded = settings !== undefined;
const requirePermissions = settings?.requirePermissions ?? false;
return (
@@ -83,46 +105,50 @@ export function PermissionsPage() {
title={t("title")}
subtitle={t("subtitle")}
actions={
-
+ canManageRoles ? (
+
+ ) : undefined
}
/>
{/* Enable/Disable Toggle */}
-
-
-
+ {registry.map((mod) => {
+ const wildcard = `${mod.key}.*`;
+ const hasWildcard = permissions.has(wildcard);
+ const allGranted =
+ hasWildcard ||
+ mod.permissions.every((p) => permissions.has(p.key));
+ const modLabel = t(mod.labelKey);
+ // toggleModuleWildcard only ever ADDS a new key (the wildcard
+ // itself) when the wildcard isn't already present — even when
+ // `allGranted` is already true via every individual permission,
+ // clicking still swaps those for the wildcard key, which the
+ // server validates as its own permission. So the only state
+ // that matters here is `hasWildcard`, not `allGranted`: mirror
+ // the server's exact check (matchPermission against the
+ // wildcard key `toggleModuleWildcard` adds), and only block the
+ // add direction — removing the wildcard is de-escalation and
+ // must stay allowed, same as isDefault: false in Fix 1.
+ const cannotAddWildcard =
+ !currentUserIsOwner && !hasWildcard && !matchPermission(myPermissionSet, wildcard);
+ const moduleCheckbox = (
+ toggleModuleWildcard(mod.key)}
+ aria-label={`${modLabel} — ${t("roleEditor.allBadge")}`}
+ />
+ );
+
+ return (
+