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:
@@ -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)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Local/Remote Node Switching
|
### Local/Remote Node Switching
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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" />);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
Reference in New Issue
Block a user