From e258f171bd30c6e218f4601f49aa81839f837d3f Mon Sep 17 00:00:00 2001 From: Fusion Date: Sat, 2 May 2026 21:22:26 -0700 Subject: [PATCH] feat(FN-3235): restore mobile graph entry point - Add mobile nav graph entry wiring in MobileNavBar to expose the graph view on mobile - Update mobile navigation behavior to preserve existing routes while including the restored graph path - Adjust MobileNavBar tests to cover the restored graph entry and expected rendering/interaction Fusion-Task-Id: FN-3235 --- .../dashboard/app/components/MobileNavBar.tsx | 24 +++++++++++++------ .../__tests__/MobileNavBar.test.tsx | 6 +++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/dashboard/app/components/MobileNavBar.tsx b/packages/dashboard/app/components/MobileNavBar.tsx index 82c080cf9..9cda7f1cb 100644 --- a/packages/dashboard/app/components/MobileNavBar.tsx +++ b/packages/dashboard/app/components/MobileNavBar.tsx @@ -210,18 +210,28 @@ export function MobileNavBar({ const showRoadmapsTopLevel = roadmapEnabled && (!skillsEnabled || view === "roadmaps"); const showSkillsTopLevel = skillsEnabled && (!roadmapEnabled || view !== "roadmaps"); const showSkillsInMore = skillsEnabled && !showSkillsTopLevel; - const primaryPluginViews = pluginDashboardViews + const isDependencyGraphView = (entry: PluginDashboardViewEntry): boolean => ( + entry.pluginId === "fusion-plugin-dependency-graph" && entry.view.viewId === "graph" + ); + const sortedPrimaryPluginViews = pluginDashboardViews .filter((entry) => entry.view.placement === "primary") .sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER)); + const dependencyGraphPluginView = pluginDashboardViews.find(isDependencyGraphView) ?? null; // Keep plugin-provided top-level tabs constrained on mobile so fixed tabs retain // reasonable touch-target width. Additional primary plugin destinations overflow into More. + // FN-3235: Always surface the dependency graph destination as the first plugin top-level tab + // on mobile so task graph navigation has a clear entry point. + const prioritizedPrimaryPluginViews = dependencyGraphPluginView + ? [dependencyGraphPluginView, ...sortedPrimaryPluginViews.filter((entry) => !isDependencyGraphView(entry))] + : sortedPrimaryPluginViews; const MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS = 1; - const topLevelPrimaryPluginViews = primaryPluginViews.slice(0, MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS); - const overflowPrimaryPluginViews = primaryPluginViews.slice(MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS); - const overflowPluginViews = [ - ...overflowPrimaryPluginViews, - ...pluginDashboardViews.filter((entry) => entry.view.placement !== "primary"), - ].sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER)); + const topLevelPrimaryPluginViews = prioritizedPrimaryPluginViews.slice(0, MAX_PRIMARY_PLUGIN_TOP_LEVEL_TABS); + const topLevelPluginViewKeys = new Set( + topLevelPrimaryPluginViews.map((entry) => `${entry.pluginId}:${entry.view.viewId}`), + ); + const overflowPluginViews = pluginDashboardViews + .filter((entry) => !topLevelPluginViewKeys.has(`${entry.pluginId}:${entry.view.viewId}`)) + .sort((a, b) => (a.view.order ?? Number.MAX_SAFE_INTEGER) - (b.view.order ?? Number.MAX_SAFE_INTEGER)); const isMoreActive = view === "documents" diff --git a/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx b/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx index c34ca2a40..9534a6f8e 100644 --- a/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx +++ b/packages/dashboard/app/components/__tests__/MobileNavBar.test.tsx @@ -105,7 +105,7 @@ describe("MobileNavBar", () => { expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull(); }); - it("renders primary plugin dashboard views as top-level tabs and keeps overflow views in More", () => { + it("renders dependency graph as a top-level tab and keeps additional plugin views in More", () => { const props = createDefaultProps(); render( { pluginDashboardViews={[ { pluginId: "fusion-plugin-dependency-graph", - view: { viewId: "graph", label: "Graph", componentPath: "./GraphView", icon: "Map", placement: "primary" }, + view: { viewId: "graph", label: "Graph", componentPath: "./GraphView", icon: "Map", placement: "more" }, }, { pluginId: "fusion-plugin-dependency-graph", @@ -129,6 +129,8 @@ describe("MobileNavBar", () => { expect(props.onChangeView).toHaveBeenCalledWith("plugin:fusion-plugin-dependency-graph:graph"); fireEvent.click(screen.getByTestId("mobile-nav-tab-more")); + expect(screen.queryByTestId("mobile-more-item-plugin-fusion-plugin-dependency-graph-graph")).toBeNull(); + const overflowItem = screen.getByTestId("mobile-more-item-plugin-fusion-plugin-dependency-graph-queue"); expect(overflowItem.querySelector(".lucide-workflow")).toBeTruthy(); fireEvent.click(overflowItem);