Files
fusion/packages/dashboard/app/plugins/pluginSlotRegistry.tsx
Fusion fccec582f4 feat(FN-3260): document static slot-host rendering contract
Documents the static slot-host rendering contract in `docs/PLUGIN_AUTHORING.md`, clarifying how plugins should interact with the rendering system.

Fusion-Task-Id: FN-3260
2026-05-04 21:21:22 -07:00

114 lines
3.4 KiB
TypeScript

import type { ComponentType, ReactNode } from "react";
import type { PluginUiSlotEntry } from "../api";
import { DroidCliProviderCard } from "../components/DroidCliProviderCard";
export interface PluginSlotHostActions {
refreshAuthProviders?: () => void;
openSettingsSection?: (section: string) => void;
openModelOnboarding?: () => void;
}
interface PluginSlotComponentProps {
entry: PluginUiSlotEntry;
actions?: PluginSlotHostActions;
}
interface PluginSlotRegistration {
pluginId: string;
slotId: string;
componentPath: string;
component: ComponentType<PluginSlotComponentProps>;
}
function DroidSettingsProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
return (
<DroidCliProviderCard
compact
authenticated={false}
onToggled={() => {
actions?.refreshAuthProviders?.();
}}
/>
);
}
function DroidOnboardingProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
return (
<DroidCliProviderCard
authenticated={false}
onToggled={() => {
actions?.refreshAuthProviders?.();
}}
/>
);
}
function DroidOnboardingSetupHelp(): ReactNode {
return (
<p className="onboarding-helper-text" data-testid="droid-onboarding-setup-help">
Tip: Enable Droid CLI to reuse your Factory AI subscription without adding an API key.
</p>
);
}
function DroidPostOnboardingRecommendation({ actions }: PluginSlotComponentProps): ReactNode {
return (
<div className="post-onboarding-recommendations__item" data-testid="droid-post-onboarding-recommendation">
<span className="post-onboarding-recommendations__item-text">
<strong>Enable Droid CLI</strong>
<span>Use your local Droid CLI session as an AI provider in Fusion.</span>
</span>
<button type="button" className="btn btn-sm" onClick={() => actions?.openSettingsSection?.("authentication")}>
Open Authentication
</button>
<button type="button" className="btn btn-sm" onClick={() => actions?.openModelOnboarding?.()}>
Open Onboarding
</button>
</div>
);
}
const REGISTRY: PluginSlotRegistration[] = [
{
pluginId: "fusion-plugin-droid-runtime",
slotId: "settings-provider-card",
componentPath: "./components/settings-provider-card.js",
component: DroidSettingsProviderCard,
},
{
pluginId: "fusion-plugin-droid-runtime",
slotId: "settings-integration-card",
componentPath: "./components/settings-integration-card.js",
component: DroidSettingsProviderCard,
},
{
pluginId: "fusion-plugin-droid-runtime",
slotId: "onboarding-provider-card",
componentPath: "./components/onboarding-provider-card.js",
component: DroidOnboardingProviderCard,
},
{
pluginId: "fusion-plugin-droid-runtime",
slotId: "onboarding-setup-help",
componentPath: "./components/onboarding-setup-help.js",
component: DroidOnboardingSetupHelp,
},
{
pluginId: "fusion-plugin-droid-runtime",
slotId: "post-onboarding-recommendation",
componentPath: "./components/post-onboarding-recommendation.js",
component: DroidPostOnboardingRecommendation,
},
];
export function resolvePluginSlotComponent(entry: PluginUiSlotEntry): ComponentType<PluginSlotComponentProps> | null {
const hit = REGISTRY.find(
(candidate) =>
candidate.pluginId === entry.pluginId
&& candidate.slotId === entry.slot.slotId
&& candidate.componentPath === entry.slot.componentPath,
);
return hit?.component ?? null;
}