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 settingsContentRef = useRef<HTMLDivElement>(null);
|
||||||
const workflowLaneSaverRef = useRef<SectionSaveHandler | null>(null);
|
const workflowLaneSaverRef = useRef<SectionSaveHandler | null>(null);
|
||||||
const registerWorkflowLaneSaver = useCallback((saver: SectionSaveHandler | 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.
|
// Modal-only: persist user-resized dialog dimensions. Embedded view fills its host and is not resizable.
|
||||||
useModalResizePersist(modalRef, resizePersistEnabled, "fusion:settings-modal-size");
|
useModalResizePersist(modalRef, resizePersistEnabled, "fusion:settings-modal-size");
|
||||||
|
|||||||
@@ -482,6 +482,34 @@ describe("SettingsModal", () => {
|
|||||||
expect(onClose).toHaveBeenCalled();
|
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 () => {
|
it("renders fallback workflow model lanes only when the default workflow declares them", async () => {
|
||||||
await setupWorkflowModelLaneTest();
|
await setupWorkflowModelLaneTest();
|
||||||
|
|
||||||
@@ -566,6 +594,36 @@ describe("SettingsModal", () => {
|
|||||||
expect(mockUpdateWorkflowSettingValues).not.toHaveBeenCalled();
|
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 () => {
|
it("resets workflow model lanes by sending null patches from the primary Settings Save", async () => {
|
||||||
await setupWorkflowModelLaneTest({
|
await setupWorkflowModelLaneTest({
|
||||||
stored: { executionProvider: "anthropic", executionModelId: "claude-sonnet-4-5" },
|
stored: { executionProvider: "anthropic", executionModelId: "claude-sonnet-4-5" },
|
||||||
|
|||||||
Reference in New Issue
Block a user