feat(FN-3208): restore mobile nodes nav and improve planning mode

This merge delivers significant testing infrastructure refactoring alongside focused UI, planning, and plugin features. The largest change splits the 20K-line `routes.test.ts` monolith into 15 granular route test files (agents, auth, automation, git, github, planning, settings, system, tasks, tasks-

Fusion-Task-Id: FN-3208
This commit is contained in:
Fusion
2026-05-02 20:44:50 -07:00
committed by gsxdsm
parent 04cf5f094c
commit e5fd33f3a4
5 changed files with 69 additions and 1 deletions

View File

@@ -171,6 +171,10 @@ Project-wide routing defaults are configured in **Settings → Node Routing**.
The Node Dashboard provides a mesh view of connected Fusion nodes. Each node can be a local instance or a remote headless node (`fn serve`). The Node Dashboard provides a mesh view of connected Fusion nodes. Each node can be a local instance or a remote headless node (`fn serve`).
Navigation:
- Desktop: Header node controls / overflow entry
- Mobile: `MobileNavBar`**More** sheet → **Nodes** (shown only when `experimentalFeatures.nodesView` is enabled)
![Nodes view](./screenshots/nodes-view.png) ![Nodes view](./screenshots/nodes-view.png)
### Local/Remote Node Switching ### Local/Remote Node Switching

View File

@@ -1046,6 +1046,7 @@ function AppInner() {
onOpenActivityLog={modalManager.openActivityLog} onOpenActivityLog={modalManager.openActivityLog}
onOpenSystemStats={modalManager.openSystemStats} onOpenSystemStats={modalManager.openSystemStats}
onOpenMailbox={() => handleTaskViewChange("mailbox")} onOpenMailbox={() => handleTaskViewChange("mailbox")}
onOpenNodes={handleOpenNodes}
mailboxUnreadCount={mailboxUnreadCount} mailboxUnreadCount={mailboxUnreadCount}
onOpenGitManager={modalManager.openGitManager} onOpenGitManager={modalManager.openGitManager}
onOpenWorkflowSteps={modalManager.openWorkflowSteps} onOpenWorkflowSteps={modalManager.openWorkflowSteps}
@@ -1072,6 +1073,7 @@ function AppInner() {
devServerView: devServerEnabled, devServerView: devServerEnabled,
todoView: todosEnabled, todoView: todosEnabled,
researchView: researchEnabled, researchView: researchEnabled,
nodesView: nodesEnabled,
}} }}
pluginDashboardViews={pluginDashboardViews} pluginDashboardViews={pluginDashboardViews}
/> />

View File

@@ -46,6 +46,7 @@ const createDefaultMobileNavProps = () => ({
onOpenSettings: vi.fn(), onOpenSettings: vi.fn(),
onOpenActivityLog: vi.fn(), onOpenActivityLog: vi.fn(),
onOpenMailbox: vi.fn(), onOpenMailbox: vi.fn(),
onOpenNodes: vi.fn(),
mailboxUnreadCount: 0, mailboxUnreadCount: 0,
onOpenGitManager: vi.fn(), onOpenGitManager: vi.fn(),
onOpenWorkflowSteps: vi.fn(), onOpenWorkflowSteps: vi.fn(),
@@ -159,9 +160,22 @@ describe("Mobile Feature Access Regression Guard", () => {
expect(screen.getByTestId("mobile-more-item-github")).toBeDefined(); expect(screen.getByTestId("mobile-more-item-github")).toBeDefined();
expect(screen.getByTestId("mobile-more-item-usage")).toBeDefined(); expect(screen.getByTestId("mobile-more-item-usage")).toBeDefined();
expect(screen.queryByTestId("mobile-more-item-chat")).toBeNull(); expect(screen.queryByTestId("mobile-more-item-chat")).toBeNull();
expect(screen.queryByTestId("mobile-more-item-nodes")).toBeNull();
expect(screen.getByTestId("mobile-more-item-settings")).toBeDefined(); expect(screen.getByTestId("mobile-more-item-settings")).toBeDefined();
}); });
it("nodes view is reachable from mobile More sheet when enabled", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} experimentalFeatures={{ nodesView: true }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
const nodesItem = screen.getByTestId("mobile-more-item-nodes");
expect(nodesItem).toBeDefined();
fireEvent.click(nodesItem);
expect(props.onOpenNodes).toHaveBeenCalledOnce();
});
it("chat is accessible via the bottom nav while remaining absent from the More sheet", () => { it("chat is accessible via the bottom nav while remaining absent from the More sheet", () => {
const props = createDefaultMobileNavProps(); const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} view="board" />); render(<MobileNavBar {...props} view="board" />);

View File

@@ -21,6 +21,7 @@ import {
Play, Play,
Settings, Settings,
Monitor, Monitor,
Network,
Search, Search,
Sparkles, Sparkles,
Target, Target,
@@ -72,7 +73,17 @@ export interface MobileNavBarProps {
/** Whether to show the skills tab */ /** Whether to show the skills tab */
showSkillsTab?: boolean; showSkillsTab?: boolean;
/** Experimental feature flags controlling visibility of nav items. */ /** Experimental feature flags controlling visibility of nav items. */
experimentalFeatures?: { insights?: boolean; roadmap?: boolean; memoryView?: boolean; devServer?: boolean; devServerView?: boolean; todoView?: boolean; researchView?: boolean }; experimentalFeatures?: {
insights?: boolean;
roadmap?: boolean;
memoryView?: boolean;
devServer?: boolean;
devServerView?: boolean;
todoView?: boolean;
researchView?: boolean;
nodesView?: boolean;
};
onOpenNodes?: () => void;
pluginDashboardViews?: PluginDashboardViewEntry[]; pluginDashboardViews?: PluginDashboardViewEntry[];
} }
@@ -123,6 +134,7 @@ export function MobileNavBar({
onViewAllProjects, onViewAllProjects,
showSkillsTab, showSkillsTab,
experimentalFeatures, experimentalFeatures,
onOpenNodes,
pluginDashboardViews = [], pluginDashboardViews = [],
}: MobileNavBarProps) { }: MobileNavBarProps) {
const mode = useViewportMode(); const mode = useViewportMode();
@@ -657,6 +669,18 @@ export function MobileNavBar({
</button> </button>
)} )}
{experimentalFeatures?.nodesView && onOpenNodes && (
<button
type="button"
className="mobile-more-item"
data-testid="mobile-more-item-nodes"
onClick={() => handleMoreAction(onOpenNodes)}
>
<Network />
<span>Nodes</span>
</button>
)}
{todoViewEnabled && ( {todoViewEnabled && (
<button <button
type="button" type="button"

View File

@@ -34,6 +34,7 @@ const createDefaultProps = () => ({
onOpenActivityLog: vi.fn(), onOpenActivityLog: vi.fn(),
onOpenSystemStats: vi.fn(), onOpenSystemStats: vi.fn(),
onOpenMailbox: vi.fn(), onOpenMailbox: vi.fn(),
onOpenNodes: vi.fn(),
mailboxUnreadCount: 0, mailboxUnreadCount: 0,
onOpenGitManager: vi.fn(), onOpenGitManager: vi.fn(),
onOpenWorkflowSteps: vi.fn(), onOpenWorkflowSteps: vi.fn(),
@@ -334,6 +335,29 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-more-item-insights")).toBeDefined(); expect(screen.getByTestId("mobile-more-item-insights")).toBeDefined();
}); });
it("shows nodes in more sheet only when nodesView is enabled", () => {
const disabledProps = createDefaultProps();
const { unmount } = render(<MobileNavBar {...disabledProps} experimentalFeatures={{}} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.queryByTestId("mobile-more-item-nodes")).toBeNull();
unmount();
const enabledProps = createDefaultProps();
render(<MobileNavBar {...enabledProps} experimentalFeatures={{ nodesView: true }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.getByTestId("mobile-more-item-nodes")).toBeDefined();
});
it("invokes onOpenNodes when nodes item is tapped", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} experimentalFeatures={{ nodesView: true }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
fireEvent.click(screen.getByTestId("mobile-more-item-nodes"));
expect(props.onOpenNodes).toHaveBeenCalledOnce();
});
it("does not show memory in more sheet when memoryView is not enabled", () => { it("does not show memory in more sheet when memoryView is not enabled", () => {
render(<MobileNavBar {...createDefaultProps()} experimentalFeatures={{}} />); render(<MobileNavBar {...createDefaultProps()} experimentalFeatures={{}} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more")); fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));