feat(FN-2726): add bulk node override support in dashboard workflows

- Extend batch task update APIs and route handlers to accept nodeId overrides alongside model settings
- Update ListView bulk edit UX to load nodes and apply node overrides through batch updates
- Add node routing visibility improvements in task detail/settings UI and align related labels/styles
- Expand dashboard/API test coverage for node override batch updates and bulk edit behavior
This commit is contained in:
Fusion
2026-04-28 13:37:37 -07:00
committed by gsxdsm
parent 5414b8a0c7
commit 35694005ef
12 changed files with 445 additions and 163 deletions

View File

@@ -1523,30 +1523,6 @@ describe("SettingsModal", () => {
});
});
it("forces enabled=true for selected active provider when saving", async () => {
renderModal();
await waitForSettingsModalReady();
await openRemoteSection();
const tailscaleToggle = screen.getByLabelText("Enable Tailscale provider config");
expect(tailscaleToggle).not.toBeChecked();
await userEvent.selectOptions(screen.getByLabelText("Active provider"), "tailscale");
await userEvent.click(screen.getByRole("button", { name: "Save Remote Settings" }));
await waitFor(() => {
expect(mockUpdateRemoteSettings).toHaveBeenCalledTimes(1);
});
expect(mockUpdateRemoteSettings).toHaveBeenCalledWith(
expect.objectContaining({
remoteActiveProvider: "tailscale",
remoteTailscaleEnabled: true,
}),
undefined,
);
});
it("toggles Cloudflare quick tunnel and hides manual cloudflare fields", async () => {
renderModal();
await waitForSettingsModalReady();
@@ -1881,4 +1857,55 @@ describe("SettingsModal", () => {
expect(screen.queryByRole("button", { name: "Dream Now" })).not.toBeInTheDocument();
});
});
describe("memory dream trigger", () => {
const openMemorySection = async () => {
const [memorySectionButton] = await screen.findAllByRole("button", { name: /^Memory$/i });
await userEvent.click(memorySectionButton);
};
it("shows Dream Now button when dreams are enabled", async () => {
mockFetchSettings.mockResolvedValueOnce({
...defaultSettings,
memoryEnabled: true,
memoryDreamsEnabled: true,
memoryDreamsSchedule: "0 4 * * *",
});
renderModal();
await waitForSettingsModalReady();
await openMemorySection();
expect(await screen.findByRole("button", { name: "Dream Now" })).toBeInTheDocument();
});
it("triggers dream processing from Dream Now button", async () => {
const addToast = vi.fn();
mockFetchSettings.mockResolvedValueOnce({
...defaultSettings,
memoryEnabled: true,
memoryDreamsEnabled: true,
});
mockTriggerMemoryDreams.mockResolvedValueOnce({ success: true, summary: "done" });
renderModal({ addToast });
await waitForSettingsModalReady();
await openMemorySection();
await userEvent.click(await screen.findByRole("button", { name: "Dream Now" }));
await waitFor(() => {
expect(mockTriggerMemoryDreams).toHaveBeenCalledWith(undefined);
});
expect(addToast).toHaveBeenCalledWith("Dream processing completed", "success");
});
it("hides Dream Now button when dreams are disabled", async () => {
renderModal();
await waitForSettingsModalReady();
await openMemorySection();
expect(screen.queryByRole("button", { name: "Dream Now" })).not.toBeInTheDocument();
});
});
});