feat(FN-3065): merge fusion/fn-3065
This merge lands v0.14.0, bringing task ingest contract documentation and todo creation wiring (FN-3065), a new Global → General settings pane with `fn --version` integration, and a fix for persisting priority changes from PATCH /tasks/:id. All changeset artifacts were consumed by the release; 53 fi Fusion-Task-Id: FN-3065
This commit is contained in:
@@ -776,6 +776,7 @@ function AppInner() {
|
||||
addToast={addToast}
|
||||
projectId={currentProject?.id}
|
||||
onPlanningMode={modalManager.openPlanningWithInitialPlan}
|
||||
onTaskCreated={(task) => ingestCreatedTasks([task])}
|
||||
/>
|
||||
</Suspense>
|
||||
</PageErrorBoundary>
|
||||
|
||||
@@ -24,13 +24,14 @@ interface TodoViewProps {
|
||||
projectId?: string;
|
||||
addToast: (message: string, type?: "success" | "error" | "info") => void;
|
||||
onPlanningMode?: (initialPlan: string) => void;
|
||||
onTaskCreated?: (task: Task) => void;
|
||||
}
|
||||
|
||||
function sortItems(items: TodoItem[]): TodoItem[] {
|
||||
return [...items].sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
}
|
||||
|
||||
export function TodoView({ projectId, addToast, onPlanningMode }: TodoViewProps) {
|
||||
export function TodoView({ projectId, addToast, onPlanningMode, onTaskCreated }: TodoViewProps) {
|
||||
const {
|
||||
lists,
|
||||
items,
|
||||
@@ -266,11 +267,12 @@ export function TodoView({ projectId, addToast, onPlanningMode }: TodoViewProps)
|
||||
source: { sourceType: "dashboard_ui" },
|
||||
};
|
||||
const task: Task = await createTask(input, projectId);
|
||||
onTaskCreated?.(task);
|
||||
addToast(`Created ${task.id} from todo`, "success");
|
||||
} catch (err) {
|
||||
addToast(`Failed to create task: ${getErrorMessage(err)}`, "error");
|
||||
}
|
||||
}, [projectId, addToast]);
|
||||
}, [projectId, addToast, onTaskCreated]);
|
||||
|
||||
const handleCreateTaskAndAssign = useCallback(async (item: TodoItem, agentId: string) => {
|
||||
try {
|
||||
@@ -281,6 +283,7 @@ export function TodoView({ projectId, addToast, onPlanningMode }: TodoViewProps)
|
||||
source: { sourceType: "dashboard_ui" },
|
||||
};
|
||||
const task: Task = await createTask(input, projectId);
|
||||
onTaskCreated?.(task);
|
||||
const assignedAgent = agents.find((agent) => agent.id === agentId);
|
||||
const agentLabel = assignedAgent?.name ?? agentId;
|
||||
addToast(`Created ${task.id} and assigned to ${agentLabel}`, "success");
|
||||
@@ -289,7 +292,7 @@ export function TodoView({ projectId, addToast, onPlanningMode }: TodoViewProps)
|
||||
} catch (err) {
|
||||
addToast(`Failed to create and assign task: ${getErrorMessage(err)}`, "error");
|
||||
}
|
||||
}, [projectId, addToast, agents]);
|
||||
}, [projectId, addToast, agents, onTaskCreated]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
@@ -440,8 +440,9 @@ describe("TodoView", () => {
|
||||
});
|
||||
|
||||
it("clicking Create Task button calls createTask with item text", async () => {
|
||||
const onTaskCreated = vi.fn();
|
||||
mockCreateTask.mockResolvedValueOnce({ id: "FN-123" });
|
||||
render(<TodoView addToast={addToast} projectId="project-1" />);
|
||||
render(<TodoView addToast={addToast} projectId="project-1" onTaskCreated={onTaskCreated} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("create-task-from-item-1"));
|
||||
|
||||
@@ -451,6 +452,7 @@ describe("TodoView", () => {
|
||||
"project-1",
|
||||
);
|
||||
});
|
||||
expect(onTaskCreated).toHaveBeenCalledWith({ id: "FN-123" });
|
||||
expect(addToast).toHaveBeenCalledWith("Created FN-123 from todo", "success");
|
||||
});
|
||||
|
||||
@@ -467,8 +469,9 @@ describe("TodoView", () => {
|
||||
});
|
||||
|
||||
it("selecting an agent creates task assigned to that agent", async () => {
|
||||
const onTaskCreated = vi.fn();
|
||||
mockCreateTask.mockResolvedValueOnce({ id: "FN-234" });
|
||||
render(<TodoView addToast={addToast} projectId="project-1" />);
|
||||
render(<TodoView addToast={addToast} projectId="project-1" onTaskCreated={onTaskCreated} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("assign-agent-for-item-1"));
|
||||
|
||||
@@ -481,6 +484,7 @@ describe("TodoView", () => {
|
||||
"project-1",
|
||||
);
|
||||
});
|
||||
expect(onTaskCreated).toHaveBeenCalledWith({ id: "FN-234" });
|
||||
expect(addToast).toHaveBeenCalledWith("Created FN-234 and assigned to Builder", "success");
|
||||
});
|
||||
|
||||
@@ -524,13 +528,15 @@ describe("TodoView", () => {
|
||||
});
|
||||
|
||||
it("error handling shows error toast", async () => {
|
||||
const onTaskCreated = vi.fn();
|
||||
mockCreateTask.mockRejectedValueOnce(new Error("boom"));
|
||||
render(<TodoView addToast={addToast} />);
|
||||
render(<TodoView addToast={addToast} onTaskCreated={onTaskCreated} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId("create-task-from-item-1"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("Failed to create task: boom", "error");
|
||||
});
|
||||
expect(onTaskCreated).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,9 +264,9 @@ describe("SettingsModal mobile adaptations", () => {
|
||||
const { container, getByText, getAllByText } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
// Authentication is first with no scope banner by default - click General to see project scope
|
||||
// Authentication is first with no scope banner by default - click the Project-scoped General section
|
||||
expect(container.querySelectorAll(".settings-scope-icon").length).toBeGreaterThan(0);
|
||||
await user.click(getAllByText("General")[0]);
|
||||
await user.click(getAllByText("General")[1]);
|
||||
|
||||
// Verify project scope banner contains icon elements (SVG from Lucide, not emoji)
|
||||
const projectBanner = container.querySelector(".settings-scope-project");
|
||||
|
||||
Reference in New Issue
Block a user