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) && (