import type { ComponentType, ReactNode } from "react"; import { lazy, Suspense, createElement } from "react"; import { useTranslation } from "react-i18next"; import type { PluginUiSlotEntry } from "../api"; import { DroidCliProviderCard } from "../components/DroidCliProviderCard"; export interface PluginSlotHostActions { refreshAuthProviders?: () => void; openSettingsSection?: (section: string) => void; openModelOnboarding?: () => void; } /* FNXC:Quality 2026-07-14-21:50: Task-detail plugin tabs need host-injected task context (taskId, worktree, projectId). Without this bag Task QA cannot scope runs, preview servers, or suggestions. */ export interface PluginSlotTaskContext { taskId?: string; worktree?: string; projectId?: string; title?: string; modifiedFiles?: string[]; } export interface PluginSlotComponentProps { entry: PluginUiSlotEntry; actions?: PluginSlotHostActions; context?: PluginSlotTaskContext; taskId?: string; worktree?: string; projectId?: string; } interface PluginSlotRegistration { pluginId: string; slotId: string; componentPath: string; component: ComponentType; } function DroidSettingsProviderCard({ actions }: PluginSlotComponentProps): ReactNode { return ( { actions?.refreshAuthProviders?.(); }} /> ); } function DroidOnboardingProviderCard({ actions }: PluginSlotComponentProps): ReactNode { return ( { actions?.refreshAuthProviders?.(); }} /> ); } function DroidOnboardingSetupHelp(): ReactNode { const { t } = useTranslation("app"); return (

{t("plugins.droidOnboardingTip", "Tip: Enable Droid CLI to reuse your Factory AI subscription without adding an API key.")}

); } function DroidPostOnboardingRecommendation({ actions }: PluginSlotComponentProps): ReactNode { const { t } = useTranslation("app"); return (
{t("plugins.droidRecommendTitle", "Enable Droid CLI")} {t("plugins.droidRecommendDesc", "Use your local Droid CLI session as an AI provider in Fusion.")}
); } const LazyQualityQaTab = lazy(async () => { const mod = await import("@fusion-plugin-examples/quality/qa-tab"); return { default: mod.QualityQaTabSlot ?? mod.default }; }); function QualityTaskDetailTab(props: PluginSlotComponentProps): ReactNode { return ( {createElement(LazyQualityQaTab, { entry: props.entry, actions: props.actions, context: props.context, taskId: props.taskId ?? props.context?.taskId, worktree: props.worktree ?? props.context?.worktree, projectId: props.projectId ?? props.context?.projectId, })} ); } const REGISTRY: PluginSlotRegistration[] = [ { pluginId: "fusion-plugin-quality", slotId: "task-detail-tab", componentPath: "./qa-tab", component: QualityTaskDetailTab, }, { 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 | null { const hit = REGISTRY.find( (candidate) => candidate.pluginId === entry.pluginId && candidate.slotId === entry.slot.slotId && candidate.componentPath === entry.slot.componentPath, ); return hit?.component ?? null; }