From 03c0348b9e712750a5c03cb4590646e0e0b969f3 Mon Sep 17 00:00:00 2001 From: Fusion Date: Tue, 5 May 2026 12:08:44 -0700 Subject: [PATCH] feat(FN-3157): add plugin dashboard view registry with nav integration Merged FN-3157 to add a plugin dashboard views system, including a plugin view registry with lazy loading, navigation integration for Header and MobileNavBar, a usePluginDashboardViews hook with cache and refetch support, and tests covering the no-loader path. Also added documentation in `docs/PLUGI Fusion-Task-Id: FN-3157 --- .changeset/fn-3157-plugin-dashboard-views.md | 5 + docs/PLUGIN_AUTHORING.md | 22 +++++ packages/dashboard/app/App.tsx | 6 +- packages/dashboard/app/components/Header.tsx | 4 +- .../dashboard/app/components/MobileNavBar.tsx | 4 +- .../app/components/__tests__/App.test.tsx | 3 +- .../__tests__/usePluginDashboardViews.test.ts | 64 +++++++++++- .../app/hooks/__tests__/useViewState.test.ts | 16 +++ .../app/hooks/usePluginDashboardViews.ts | 19 +++- packages/dashboard/app/hooks/useViewState.ts | 7 +- .../app/plugins/PluginDashboardViewHost.tsx | 22 +---- .../__tests__/pluginViewRegistry.test.tsx | 48 +++++++++ .../app/plugins/pluginViewRegistry.tsx | 98 +++++++++++-------- .../src/__tests__/plugin-routes.test.ts | 10 ++ 14 files changed, 253 insertions(+), 75 deletions(-) create mode 100644 .changeset/fn-3157-plugin-dashboard-views.md create mode 100644 packages/dashboard/app/plugins/__tests__/pluginViewRegistry.test.tsx diff --git a/.changeset/fn-3157-plugin-dashboard-views.md b/.changeset/fn-3157-plugin-dashboard-views.md new file mode 100644 index 000000000..98546898d --- /dev/null +++ b/.changeset/fn-3157-plugin-dashboard-views.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Add plugin dashboard view discovery and navigation integration via `GET /api/plugins/dashboard-views`, plugin view ID persistence (`plugin:${pluginId}:${viewId}`), and static host-side plugin view registry rendering. diff --git a/docs/PLUGIN_AUTHORING.md b/docs/PLUGIN_AUTHORING.md index dada3827f..b0238aeff 100644 --- a/docs/PLUGIN_AUTHORING.md +++ b/docs/PLUGIN_AUTHORING.md @@ -555,6 +555,28 @@ Current host constraints: - `componentPath` is stored for authoring symmetry/future expansion, but render resolution is currently done through a host-side static registry (`pluginId + viewId`) - Use stable IDs; runtime view key format is `plugin:${pluginId}:${viewId}` +### Static host registry model + +Dashboard view components are resolved from a host-side registry and must be explicitly registered: + +```ts +import { lazy } from "react"; +import { registerPluginView } from "../app/plugins/pluginViewRegistry"; + +registerPluginView( + "fusion-plugin-dependency-graph", + "graph", + lazy(() => import("@fusion-plugin-examples/dependency-graph/dashboard-view")), +); +``` + +The host then renders plugin views via `PluginDashboardViewHost` using the composite ID. + +Placement guidance: +- `primary`: top-level nav tab (host may limit count on mobile) +- `overflow`: desktop header overflow menu +- `more`: mobile More sheet / secondary nav surfaces + --- ## 9. Registering Agent Runtimes diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx index 20f624c58..426128e30 100644 --- a/packages/dashboard/app/App.tsx +++ b/packages/dashboard/app/App.tsx @@ -49,6 +49,7 @@ import { useViewState, type TaskView } from "./hooks/useViewState"; import { useNavigationHistory } from "./hooks/useNavigationHistory"; import { usePluginDashboardViews } from "./hooks/usePluginDashboardViews"; import { PluginDashboardViewHost } from "./plugins/PluginDashboardViewHost"; +import { isPluginViewId } from "./plugins/pluginViewRegistry"; import { useProjectActions } from "./hooks/useProjectActions"; import { useTaskHandlers } from "./hooks/useTaskHandlers"; import { useRemoteNodeData } from "./hooks/useRemoteNodeData"; @@ -448,6 +449,7 @@ function AppInner() { // Redirect to board if feature-gated views are disabled. useEffect(() => { if (!settingsLoaded) return; + if (isPluginViewId(taskView)) return; if (taskView === "skills" && !skillsEnabled) { handleChangeTaskView("board"); } @@ -867,7 +869,7 @@ function AppInner() { } // Project view - if (taskView.startsWith("plugin:")) { + if (isPluginViewId(taskView)) { return ( setShellConnectionManagerOpen(true)} /> ) : undefined} /> - {viewMode === "project" && currentProject && taskView !== "chat" && taskView !== "mailbox" && taskView !== "insights" && taskView !== "devserver" && taskView !== "dev-server" && !taskView.startsWith("plugin:") && ( + {viewMode === "project" && currentProject && taskView !== "chat" && taskView !== "mailbox" && taskView !== "insights" && taskView !== "devserver" && taskView !== "dev-server" && !isPluginViewId(taskView) && (