FN-7964: fix Project Models workflow model lane persistence
Keep pending Project Models workflow lane overrides registered across section navigation so primary Settings Save still flushes them. - Stop clearing the workflow-lane saver ref on Project Models unmount - Add regression coverage for save-after-section-nav and reload override display - Add patch changeset for the dashboard fix Files changed: .changeset/fn-7964-project-models-workflow-lanes.md | 7 +++++++ packages/dashboard/app/components/SettingsModal.tsx | 8 +++++++- packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7964 Fusion-Task-Lineage: ed4e6268-d5b7-44ba-bbcc-2d44b301403e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7964-project-models-workflow-lanes.md
Normal file
7
.changeset/fn-7964-project-models-workflow-lanes.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix Project Models workflow model lane saves.
|
||||
category: fix
|
||||
dev: Keeps pending default-workflow model lane overrides registered for Settings Save after section navigation.
|
||||
@@ -999,7 +999,13 @@ export function SettingsModal({
|
||||
const settingsContentRef = useRef<HTMLDivElement>(null);
|
||||
const workflowLaneSaverRef = useRef<SectionSaveHandler | null>(null);
|
||||
const registerWorkflowLaneSaver = useCallback((saver: SectionSaveHandler | null) => {
|
||||
workflowLaneSaverRef.current = saver;
|
||||
/*
|
||||
FNXC:ProjectModelsWorkflowLanes 2026-07-14-09:07:
|
||||
Project Models workflow lane edits are workflow setting-values, not normal project settings. Keep the latest saver registered across section unmounts so the primary Settings Save still flushes project-scoped workflow overrides when operators navigate away before saving.
|
||||
*/
|
||||
if (saver) {
|
||||
workflowLaneSaverRef.current = saver;
|
||||
}
|
||||
}, []);
|
||||
// Modal-only: persist user-resized dialog dimensions. Embedded view fills its host and is not resizable.
|
||||
useModalResizePersist(modalRef, resizePersistEnabled, "fusion:settings-modal-size");
|
||||
|
||||
@@ -482,6 +482,34 @@ describe("SettingsModal", () => {
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders saved workflow model lane values as project overrides after reload", async () => {
|
||||
const expectedPatch = { planningProvider: "openai", planningModelId: "gpt-4o" };
|
||||
mockUpdateWorkflowSettingValues.mockResolvedValue({
|
||||
stored: expectedPatch,
|
||||
effective: expectedPatch,
|
||||
orphaned: [],
|
||||
});
|
||||
await setupWorkflowModelLaneTest();
|
||||
|
||||
await settingsModalUser.click(screen.getByLabelText("Plan/Triage Model"));
|
||||
await settingsModalUser.click(await screen.findByText("GPT-4o"));
|
||||
await settingsModalUser.click(screen.getByRole("button", { name: "Save" }));
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateWorkflowSettingValues).toHaveBeenCalledWith("workflow-custom", expectedPatch, "proj-1");
|
||||
});
|
||||
|
||||
cleanup();
|
||||
mockFetchWorkflow.mockClear();
|
||||
mockFetchWorkflowSettingValues.mockClear();
|
||||
mockUpdateWorkflowSettingValues.mockClear();
|
||||
await setupWorkflowModelLaneTest({ stored: expectedPatch, effective: expectedPatch });
|
||||
|
||||
const lane = screen.getByTestId("workflow-model-lane-planning");
|
||||
expect(within(lane).getByText("Override (Project)")).toBeInTheDocument();
|
||||
expect(within(lane).getByText("GPT-4o")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-model-lane-execution")).toHaveTextContent("Inherited (Workflow)");
|
||||
});
|
||||
|
||||
it("renders fallback workflow model lanes only when the default workflow declares them", async () => {
|
||||
await setupWorkflowModelLaneTest();
|
||||
|
||||
@@ -566,6 +594,36 @@ describe("SettingsModal", () => {
|
||||
expect(mockUpdateWorkflowSettingValues).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves pending workflow lane edits when Project Models unmounts before primary Save", async () => {
|
||||
const expectedPatch = { planningProvider: "openai", planningModelId: "gpt-4o" };
|
||||
mockUpdateWorkflowSettingValues.mockResolvedValue({
|
||||
stored: expectedPatch,
|
||||
effective: expectedPatch,
|
||||
orphaned: [],
|
||||
});
|
||||
const onClose = vi.fn();
|
||||
await setupWorkflowModelLaneTest({ renderProps: { onClose } });
|
||||
|
||||
await settingsModalUser.click(screen.getByLabelText("Plan/Triage Model"));
|
||||
await settingsModalUser.click(await screen.findByText("GPT-4o"));
|
||||
expect(within(screen.getByTestId("workflow-model-lane-planning")).getByText("GPT-4o")).toBeInTheDocument();
|
||||
|
||||
await settingsModalUser.click(screen.getByRole("button", { name: "General" }));
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("workflow-model-lane-planning")).not.toBeInTheDocument();
|
||||
});
|
||||
await settingsModalUser.click(screen.getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateWorkflowSettingValues).toHaveBeenCalledWith(
|
||||
"workflow-custom",
|
||||
expectedPatch,
|
||||
"proj-1",
|
||||
);
|
||||
});
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resets workflow model lanes by sending null patches from the primary Settings Save", async () => {
|
||||
await setupWorkflowModelLaneTest({
|
||||
stored: { executionProvider: "anthropic", executionModelId: "claude-sonnet-4-5" },
|
||||
|
||||
Reference in New Issue
Block a user