feat: add a permission-gated UI action registry and action slots for plugins

Moves the terminal toolbar's hardwired AI button into a slot the ssh-terminal
plugin offers and the ai plugin fills, gated on ai.services.use.
This commit is contained in:
LukeGus committed 2026-09-21 16:56:15 -05:00
1 parent 225f618337
commit 2d6c1cdecb
29 files changed
+1971 -40

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
import { useMemo, useSyncExternalStore } from "react";
import {
getSlotContributions,
subscribeToActionRegistry,
getActionPermission,
type SlotContribution,
} from "@/shell/action-registry";
import { usePermissions } from "@/hooks/use-permissions";
/**
* The contributions in a slot that the current user may actually see.
*
* A slot owner calls this without knowing which plugins contributed, or
* whether any did. Contributions the user lacks the permission for are
* dropped entirely rather than disabled, which is how admin-gated UI already
* behaves elsewhere in the app.
*/
export function useActionSlot(slotId: string): SlotContribution[] {
const contributions = useSyncExternalStore(
subscribeToActionRegistry,
() => getSlotContributions(slotId),
() => getSlotContributions(slotId),
);
const { has, loaded } = usePermissions();
return useMemo(() => {
// Render nothing until the grants are known. A button that appears and
// then vanishes is worse than one that appears a moment late.
if (!loaded) return [];
return contributions.filter((contribution) => {
const permission =
getActionPermission(contribution.actionId) ?? undefined;
return !permission || has(permission);
});
}, [contributions, has, loaded]);
}