feat(FN-4801): merge fusion/fn-4801

This commit is contained in:
gsxdsm
2026-05-16 20:13:30 -07:00
parent 9a249dfe2b
commit 4d5e296049
4 changed files with 103 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Dashboard top progress bar now also reflects task list revalidation, not just project loading.

View File

@@ -382,7 +382,7 @@ function AppInner() {
// Tasks hook with project context and search query
// SSE is only enabled for board/list views to free connection slots for mission detail fetches
const taskSseEnabled = taskView === "board" || taskView === "list";
const { tasks, createTask, moveTask, pauseTask, unpauseTask, deleteTask, mergeTask, retryTask, resetTask, updateTask, duplicateTask, archiveTask, unarchiveTask, archiveAllDone, loadArchivedTasks, refreshTasks, ingestCreatedTasks, lastFetchTimeMs } = useTasks(
const { tasks, isStale, createTask, moveTask, pauseTask, unpauseTask, deleteTask, mergeTask, retryTask, resetTask, updateTask, duplicateTask, archiveTask, unarchiveTask, archiveAllDone, loadArchivedTasks, refreshTasks, ingestCreatedTasks, lastFetchTimeMs } = useTasks(
{
...(currentProject ? { projectId: currentProject.id } : {}),
searchQuery: searchQuery || undefined,
@@ -1643,6 +1643,10 @@ function AppInner() {
isOnboardingCompleted() &&
!isPostOnboardingDismissed();
// Top progress bar reflects any in-flight revalidation: projects, current-project, or tasks.
// Add new sources here, not inside TopProgressBar.
const isRevalidating = projectsLoading || currentProjectLoading || isStale;
return (
<NavigationHistoryProvider value={{ pushNav, replaceCurrent }}>
<FileBrowserProvider openFile={openFileInBrowser}>
@@ -1654,7 +1658,7 @@ function AppInner() {
</>
) : (
<>
<TopProgressBar visible={projectsLoading || currentProjectLoading} />
<TopProgressBar visible={isRevalidating} />
<Header
shellHost={shellHost.host}
onOpenSettings={openSettingsWithNav}

View File

@@ -603,6 +603,7 @@ beforeEach(() => {
mockUseTasks.mockReset();
mockUseTasks.mockImplementation(() => ({
tasks: [],
isStale: false,
createTask: mockCreateTask,
moveTask: vi.fn(),
pauseTask: vi.fn(),
@@ -706,6 +707,84 @@ describe("FN-4250 FileBrowserProvider coverage", () => {
expect(screen.queryByTestId("fb-probe-loader")).not.toBeInTheDocument();
});
it("FN-4801: keeps top progress bar visible while tasks are stale", () => {
mockProjectsState.loading = false;
mockProjectsState.projects = [
{ id: DEFAULT_PROJECT_ID, name: "Test Project", path: "/test", status: "active", isolationMode: "in-process", createdAt: "", updatedAt: "" },
];
mockCurrentProjectState.loading = false;
mockUseTasks.mockImplementation(() => ({
tasks: [],
isStale: true,
createTask: mockCreateTask,
moveTask: vi.fn(),
pauseTask: vi.fn(),
unpauseTask: vi.fn(),
deleteTask: vi.fn(),
mergeTask: vi.fn(),
retryTask: vi.fn(),
resetTask: vi.fn(),
updateTask: vi.fn(),
duplicateTask: vi.fn(),
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
loadArchivedTasks: vi.fn(),
refreshTasks: vi.fn(),
ingestCreatedTasks: vi.fn(),
lastFetchTimeMs: Date.now(),
}));
render(<App />);
const progressBar = screen.getByRole("progressbar", { name: "Loading" });
expect(progressBar).toHaveAttribute("aria-busy", "true");
expect(progressBar).toHaveAttribute("data-visible", "true");
});
it("FN-4801: hides top progress bar on next render when tasks are fresh", () => {
mockProjectsState.loading = false;
mockProjectsState.projects = [
{ id: DEFAULT_PROJECT_ID, name: "Test Project", path: "/test", status: "active", isolationMode: "in-process", createdAt: "", updatedAt: "" },
];
mockCurrentProjectState.loading = false;
const taskHookState = { isStale: true };
mockUseTasks.mockImplementation(() => ({
tasks: [],
isStale: taskHookState.isStale,
createTask: mockCreateTask,
moveTask: vi.fn(),
pauseTask: vi.fn(),
unpauseTask: vi.fn(),
deleteTask: vi.fn(),
mergeTask: vi.fn(),
retryTask: vi.fn(),
resetTask: vi.fn(),
updateTask: vi.fn(),
duplicateTask: vi.fn(),
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
archiveAllDone: vi.fn(),
loadArchivedTasks: vi.fn(),
refreshTasks: vi.fn(),
ingestCreatedTasks: vi.fn(),
lastFetchTimeMs: Date.now(),
}));
const { rerender } = render(<App />);
const progressBar = screen.getByRole("progressbar", { name: "Loading" });
expect(progressBar).toHaveAttribute("aria-busy", "true");
expect(progressBar).toHaveAttribute("data-visible", "true");
taskHookState.isStale = false;
rerender(<App />);
expect(progressBar).toHaveAttribute("aria-busy", "false");
expect(progressBar).toHaveAttribute("data-visible", "false");
});
it("FN-4250: ChatView branch is inside FileBrowserProvider", async () => {
localStorage.setItem(taskViewStorageKey(), "chat");

View File

@@ -18,4 +18,17 @@ describe("TopProgressBar", () => {
expect(progressBar).toHaveAttribute("aria-busy", "false");
expect(progressBar).toHaveAttribute("data-visible", "false");
});
it("derives visibility only from the visible prop across rerenders", () => {
const { rerender } = render(<TopProgressBar visible={true} />);
const progressBar = screen.getByRole("progressbar", { name: "Loading" });
expect(progressBar).toHaveAttribute("aria-busy", "true");
expect(progressBar).toHaveAttribute("data-visible", "true");
rerender(<TopProgressBar visible={false} />);
expect(progressBar).toHaveAttribute("aria-busy", "false");
expect(progressBar).toHaveAttribute("data-visible", "false");
});
});