feat(FN-3264): add droid plugin, improve agent detail view, and fix researc

This merge adds Agent Detail view improvements including model favoriting, wires in the droid bundled plugin, updates mobile split layout with proper safe-area padding, and adds research task-view persistence with associated test coverage across Header, MobileNavBar, App, and view state hooks.

Fusion-Task-Id: FN-3264
This commit is contained in:
Fusion
2026-05-04 09:07:55 -07:00
committed by gsxdsm
parent 5c105d0f03
commit 6fa487ee7f
4 changed files with 95 additions and 3 deletions

View File

@@ -1453,6 +1453,27 @@ describe("App view switching", () => {
localStorage.removeItem(taskViewStorageKey());
});
it("initializes research view from persisted task-view when feature-enabled", async () => {
localStorage.setItem("kb-dashboard-view-mode", "project");
localStorage.setItem(taskViewStorageKey(), "research");
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
...defaultSettings,
experimentalFeatures: {
...defaultSettings.experimentalFeatures,
researchView: true,
},
});
render(<App />);
await waitFor(() => {
expect(screen.getByTestId("research-view")).toBeInTheDocument();
});
localStorage.removeItem("kb-dashboard-view-mode");
localStorage.removeItem(taskViewStorageKey());
});
it("falls back to board when research view is feature-disabled", async () => {
localStorage.setItem("kb-dashboard-view-mode", "project");
localStorage.setItem(taskViewStorageKey(), "research");

View File

@@ -211,13 +211,29 @@ describe("Header", () => {
expect(screen.getByTestId("view-toggle-overflow-trigger")).toBeDefined();
});
it("renders view overflow trigger for research even when all optional feature flags are false", () => {
it("does not render research in overflow when researchView is disabled", () => {
renderHeader({
onChangeView: noop,
showSkillsTab: false,
experimentalFeatures: { insights: false, roadmap: false, memoryView: false, devServerView: false },
experimentalFeatures: { insights: false, roadmap: false, memoryView: false, devServerView: false, researchView: false },
});
expect(screen.getByTestId("view-toggle-overflow-trigger")).toBeDefined();
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
expect(screen.queryByTestId("view-overflow-research")).toBeNull();
});
it("routes to research from the desktop view overflow when enabled", () => {
const onChangeView = vi.fn();
renderHeader({
onChangeView,
experimentalFeatures: { researchView: true },
});
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
fireEvent.click(screen.getByTestId("view-overflow-research"));
expect(onChangeView).toHaveBeenCalledWith("research");
expect(screen.queryByTestId("view-overflow-research")).toBeNull();
});
});

View File

@@ -381,6 +381,18 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-more-item-insights")).toBeDefined();
});
it("shows research in more sheet when experimentalFeatures.researchView is true", () => {
render(<MobileNavBar {...createDefaultProps()} experimentalFeatures={{ researchView: true }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.getByTestId("mobile-more-item-research")).toBeDefined();
});
it("does not show research in more sheet when experimentalFeatures.researchView is false", () => {
render(<MobileNavBar {...createDefaultProps()} experimentalFeatures={{ researchView: false }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.queryByTestId("mobile-more-item-research")).toBeNull();
});
it("shows nodes in more sheet only when nodesView is enabled", () => {
const disabledProps = createDefaultProps();
const { unmount } = render(<MobileNavBar {...disabledProps} experimentalFeatures={{}} />);
@@ -427,6 +439,17 @@ describe("MobileNavBar", () => {
expect(props.onChangeView).toHaveBeenCalledWith("insights");
});
it("research item in more sheet calls onChangeView with 'research'", () => {
const props = createDefaultProps();
const { container } = render(<MobileNavBar {...props} experimentalFeatures={{ researchView: true }} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
fireEvent.click(screen.getByTestId("mobile-more-item-research"));
expect(container.querySelector(".mobile-more-sheet")).toBeNull();
expect(props.onChangeView).toHaveBeenCalledWith("research");
});
it("activity log item in more sheet calls onOpenActivityLog", () => {
const props = createDefaultProps();
const { container } = render(<MobileNavBar {...props} />);

View File

@@ -232,6 +232,22 @@ describe("useViewState", () => {
});
});
it("reads saved research taskView from scoped localStorage on init", async () => {
localStorage.setItem("kb:proj_123:kb-dashboard-task-view", "research");
const { result } = renderHook(() =>
useViewState(
createOptions({
currentProject: PROJECT,
}),
),
);
await waitFor(() => {
expect(result.current.taskView).toBe("research");
});
});
it("persists insights taskView changes to scoped localStorage", async () => {
const { result } = renderHook(() =>
useViewState(
@@ -248,6 +264,22 @@ describe("useViewState", () => {
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("insights");
});
it("persists research taskView changes to scoped localStorage", async () => {
const { result } = renderHook(() =>
useViewState(
createOptions({
currentProject: PROJECT,
}),
),
);
await act(async () => {
result.current.setTaskView("research");
});
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("research");
});
it("restores dev-server task view and normalizes legacy devserver values", async () => {
localStorage.setItem("kb:proj_123:kb-dashboard-task-view", "dev-server");