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
This commit is contained in:
Fusion
2026-05-02 21:22:26 -07:00
committed by gsxdsm
parent 76f9504773
commit e258f171bd
2 changed files with 21 additions and 9 deletions

View File

@@ -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"

View File

@@ -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(
<MobileNavBar
@@ -113,7 +113,7 @@ describe("MobileNavBar", () => {
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);