feat(FN-3079): add plugin dashboard views and dependency graph plugin

- Add plugin dashboard view registration and hosting across core, dashboard routes, and plugin SDK exports
- Integrate plugin-provided views into app navigation, mobile/header UI, and view state hooks with coverage
- Add fusion-plugin-dependency-graph example plugin with persisted storage, dashboard view UI, and manifest wiring
- Update plugin authoring and architecture docs for dashboard view extension points
- Add a changeset for @runfusion/fusion covering plugin dashboard view support

Fusion-Task-Id: FN-3079
This commit is contained in:
Fusion
2026-05-01 23:32:32 -07:00
committed by gsxdsm
parent a284138167
commit 0597b34eea
38 changed files with 1264 additions and 16 deletions

View File

@@ -1288,6 +1288,42 @@ describe("PluginLoader", () => {
});
});
describe("getPluginDashboardViews", () => {
it("returns empty array when no plugins loaded", async () => {
await pluginStore.init();
const loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });
expect(loader.getPluginDashboardViews()).toEqual([]);
});
it("aggregates dashboard views from multiple plugins and keeps uiSlots separate", async () => {
await pluginStore.init();
const loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });
(loader as any).plugins.set("views-a", {
manifest: makeManifest({ id: "views-a" }),
state: "started",
hooks: {},
uiSlots: [{ slotId: "task-detail-tab", label: "Tab", componentPath: "./tab.js" }],
dashboardViews: [{ viewId: "graph", label: "Graph", componentPath: "./graph.js", placement: "more" }],
} as FusionPlugin);
(loader as any).plugins.set("views-b", {
manifest: makeManifest({ id: "views-b" }),
state: "started",
hooks: {},
dashboardViews: [{ viewId: "timeline", label: "Timeline", componentPath: "./timeline.js" }],
} as FusionPlugin);
const views = loader.getPluginDashboardViews();
expect(views).toHaveLength(2);
expect(views.map((entry) => entry.pluginId + ":" + entry.view.viewId)).toEqual([
"views-a:graph",
"views-b:timeline",
]);
expect(loader.getPluginUiSlots()).toHaveLength(1);
});
});
// ── getPluginRuntimes ─────────────────────────────────────────────
describe("getPluginRuntimes", () => {

View File

@@ -140,6 +140,7 @@ export type {
PluginRouteDefinition,
PluginRouteMethod,
PluginUiSlotDefinition,
PluginDashboardViewDefinition,
PluginRuntimeManifestMetadata,
PluginRuntimeFactory,
PluginRuntimeRegistration,

View File

@@ -22,6 +22,7 @@ import type {
PluginToolDefinition,
PluginRouteDefinition,
PluginUiSlotDefinition,
PluginDashboardViewDefinition,
PluginRuntimeRegistration,
PluginInstallation,
PluginSkillContribution,
@@ -775,6 +776,22 @@ export class PluginLoader extends EventEmitter<{
return slots;
}
/**
* Get all top-level dashboard view definitions from loaded plugins.
*/
getPluginDashboardViews(): Array<{ pluginId: string; view: PluginDashboardViewDefinition }> {
const views: Array<{ pluginId: string; view: PluginDashboardViewDefinition }> = [];
for (const [pluginId, plugin] of this.plugins) {
if (plugin.dashboardViews) {
for (const view of plugin.dashboardViews) {
views.push({ pluginId, view });
}
}
}
return views;
}
/**
* Get all runtime registrations from loaded plugins.
* Returns plugin ownership metadata along with the runtime registration.

View File

@@ -179,6 +179,28 @@ export interface PluginUiSlotDefinition {
componentPath: string;
}
/**
* Top-level dashboard view definition for plugin-provided navigation destinations.
* This is separate from embedded uiSlots and is rendered via host-managed registry.
*/
export interface PluginDashboardViewDefinition {
/** Unique view identifier within a plugin namespace. */
viewId: string;
/** Human-readable label shown in dashboard navigation. */
label: string;
/**
* Path to module exporting the dashboard view component.
* Stored for authoring symmetry/future expansion; host currently resolves via static registry.
*/
componentPath: string;
/** Optional icon name (lucide-react icon name or custom icon identifier). */
icon?: string;
/** Optional sort order for nav presentation. Lower numbers appear first. */
order?: number;
/** Preferred navigation placement for this top-level view. */
placement?: "primary" | "more";
}
// ── Plugin Runtimes ─────────────────────────────────────────────────
/**
@@ -356,6 +378,8 @@ export interface FusionPlugin {
tools?: PluginToolDefinition[];
routes?: PluginRouteDefinition[];
uiSlots?: PluginUiSlotDefinition[];
/** Plugin-contributed top-level dashboard views. */
dashboardViews?: PluginDashboardViewDefinition[];
/** Agent runtime registration for providing custom runtime implementations */
runtime?: PluginRuntimeRegistration;
/** Plugin-contributed skills surfaced by the skill resolver. */