Files
fusion/packages/dashboard/app/components/settings/sections/PluginsSection.tsx
gsxdsm 0e2aa49f8d FN-8520: hide uninstalled runtimes from Settings
Keep Settings runtime navigation synchronized with installed runtime plugins.

- Filter runtime sections to installed plugins while preserving disabled installed runtimes
- Refresh runtime navigation after plugin lifecycle events and direct plugin mutations
- Add Settings coverage and document runtime visibility behavior

Files changed:
 .changeset/fn-8520-runtime-sidebar.md              |  7 +++
 docs/dashboard-guide.md                            |  2 +-
 .../dashboard/app/components/PluginManager.tsx     | 10 +++-
 .../dashboard/app/components/SettingsModal.tsx     | 70 +++++++++++++++++++++-
 .../__tests__/SettingsModal.general.test.tsx       | 58 ++++++++++++++++++
 .../__tests__/SettingsModal.test-harness.tsx       |  2 +
 .../components/__tests__/settings-mobile.test.tsx  |  1 +
 .../settings/sections/PluginsSection.tsx           |  6 +-
 8 files changed, 149 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-8520

Fusion-Task-Lineage: 914ae503-e1b0-4051-bc01-4da950bf55b3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-22 20:38:25 -07:00

40 lines
3.2 KiB
TypeScript

import { lazy, Suspense } from "react";
import { PluginSlot } from "../../PluginSlot";
import type { ToastType } from "../../../hooks/useToast";
import { useTranslation } from "react-i18next";
const PluginManager = lazy(() => import("../../PluginManager").then((m) => ({ default: m.PluginManager })));
const PiExtensionsManager = lazy(() => import("../../PiExtensionsManager").then((m) => ({ default: m.PiExtensionsManager })));
export type PluginsSubsectionId = "fusion-plugins" | "pi-extensions";
export interface PluginsSectionProps {
projectId?: string;
addToast: (message: string, type?: ToastType) => void;
activePluginsSubsection: PluginsSubsectionId;
setActivePluginsSubsection: (id: PluginsSubsectionId) => void;
/** Invalidates Settings-owned runtime navigation after a direct mutation. */
onPluginsChanged?: () => void;
}
export function PluginsSection({ projectId, addToast, activePluginsSubsection, setActivePluginsSubsection, onPluginsChanged, }: PluginsSectionProps) {
const { t } = useTranslation("app");
return (<>
<h4 className="settings-section-heading">{t("settings.plugins.plugins", "Plugins")}</h4>
<div className="settings-plugins-subsection-toggle" role="tablist" aria-label={t("settings.plugins.pluginManagerType", "Plugin manager type")}>
<button type="button" id="plugins-tab-fusion-plugins" role="tab" aria-controls="plugins-panel-fusion-plugins" aria-selected={activePluginsSubsection === "fusion-plugins"} tabIndex={activePluginsSubsection === "fusion-plugins" ? 0 : -1} className={`settings-plugins-subsection-btn${activePluginsSubsection === "fusion-plugins" ? " active" : ""}`} onClick={() => setActivePluginsSubsection("fusion-plugins")}>{t("settings.plugins.fusionPlugins", " Fusion Plugins ")}</button>
<button type="button" id="plugins-tab-pi-extensions" role="tab" aria-controls="plugins-panel-pi-extensions" aria-selected={activePluginsSubsection === "pi-extensions"} tabIndex={activePluginsSubsection === "pi-extensions" ? 0 : -1} className={`settings-plugins-subsection-btn${activePluginsSubsection === "pi-extensions" ? " active" : ""}`} onClick={() => setActivePluginsSubsection("pi-extensions")}>{t("settings.plugins.piExtensions", " Pi Extensions ")}</button>
</div>
<div id="plugins-panel-fusion-plugins" role="tabpanel" aria-labelledby="plugins-tab-fusion-plugins" className="settings-plugins-subsection-panel" hidden={activePluginsSubsection !== "fusion-plugins"}>
{activePluginsSubsection === "fusion-plugins" && (<>
<Suspense fallback={null}>
<PluginManager addToast={addToast} projectId={projectId} onPluginsChanged={onPluginsChanged}/>
</Suspense>
<PluginSlot slotId="settings-section" projectId={projectId}/>
</>)}
</div>
<div id="plugins-panel-pi-extensions" role="tabpanel" aria-labelledby="plugins-tab-pi-extensions" className="settings-plugins-subsection-panel" hidden={activePluginsSubsection !== "pi-extensions"}>
{activePluginsSubsection === "pi-extensions" && (<Suspense fallback={null}>
<PiExtensionsManager addToast={addToast} projectId={projectId}/>
</Suspense>)}
</div>
</>);
}
export default PluginsSection;