Sync state back to snapshot when child form un-modifies and remount on undo

This commit is contained in:
Josh Hawkins 2026-05-16 16:24:29 -05:00
parent 7f550e5d07
commit 8fd9726220

View File

@ -120,6 +120,7 @@ export default function DetectorsAndModelSettingsView({
const [snapshot, setSnapshot] = useState<PageState | null>(null); const [snapshot, setSnapshot] = useState<PageState | null>(null);
const [state, setState] = useState<PageState | null>(null); const [state, setState] = useState<PageState | null>(null);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [resetKey, setResetKey] = useState(0);
const [restartDialogOpen, setRestartDialogOpen] = useState(false); const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const { send: sendRestart } = useRestart(); const { send: sendRestart } = useRestart();
const [childPending, setChildPending] = useState< const [childPending, setChildPending] = useState<
@ -225,21 +226,28 @@ export default function DetectorsAndModelSettingsView({
useEffect(() => { useEffect(() => {
const detectorsPending = childPending["detectors"]; const detectorsPending = childPending["detectors"];
if (detectorsPending) { setState((prev) => {
setState((prev) => if (!prev || !snapshot) return prev;
prev ? { ...prev, detectors: detectorsPending } : prev, // When the embedded form un-modifies (data returns to baseline) it clears
); // its entry from childPending — fall back to snapshot so state.detectors
} // doesn't keep a stale value the user has visually reverted.
return {
...prev,
detectors: detectorsPending ?? snapshot.detectors,
};
});
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [childPending["detectors"]]); }, [childPending["detectors"]]);
useEffect(() => { useEffect(() => {
const modelPending = childPending["model"]; const modelPending = childPending["model"];
if (modelPending) { setState((prev) => {
setState((prev) => if (!prev || !snapshot) return prev;
prev ? { ...prev, customModel: modelPending } : prev, return {
); ...prev,
} customModel: modelPending ?? snapshot.customModel,
};
});
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [childPending["model"]]); }, [childPending["model"]]);
@ -308,6 +316,7 @@ export default function DetectorsAndModelSettingsView({
// Re-derive snapshot from the freshly saved state so isDirty resets. // Re-derive snapshot from the freshly saved state so isDirty resets.
setSnapshot({ ...state }); setSnapshot({ ...state });
setChildPending({}); setChildPending({});
setResetKey((k) => k + 1);
if (detectorChanged) { if (detectorChanged) {
toast.success(t("detectorsAndModel.toast.saveSuccessRestart"), { toast.success(t("detectorsAndModel.toast.saveSuccessRestart"), {
@ -344,6 +353,10 @@ export default function DetectorsAndModelSettingsView({
if (snapshot) { if (snapshot) {
setState(snapshot); setState(snapshot);
setChildPending({}); setChildPending({});
// Force the embedded forms to re-mount so their internal dirty/baseline
// state is rebuilt from the current config — clearing childPending alone
// doesn't reset BaseSection's internal tracking.
setResetKey((k) => k + 1);
} }
}, [snapshot]); }, [snapshot]);
@ -393,6 +406,7 @@ export default function DetectorsAndModelSettingsView({
<div className="space-y-6"> <div className="space-y-6">
<SettingsGroupCard title={t("detectorsAndModel.cardTitles.detector")}> <SettingsGroupCard title={t("detectorsAndModel.cardTitles.detector")}>
<ConfigSectionTemplate <ConfigSectionTemplate
key={`detectors-${resetKey}`}
sectionKey="detectors" sectionKey="detectors"
level="global" level="global"
showOverrideIndicator={false} showOverrideIndicator={false}
@ -591,6 +605,7 @@ export default function DetectorsAndModelSettingsView({
<TabsContent value="custom"> <TabsContent value="custom">
<ConfigSectionTemplate <ConfigSectionTemplate
key={`model-${resetKey}`}
sectionKey="model" sectionKey="model"
level="global" level="global"
showOverrideIndicator={false} showOverrideIndicator={false}
@ -604,6 +619,7 @@ export default function DetectorsAndModelSettingsView({
</Tabs> </Tabs>
) : ( ) : (
<ConfigSectionTemplate <ConfigSectionTemplate
key={`model-${resetKey}`}
sectionKey="model" sectionKey="model"
level="global" level="global"
showOverrideIndicator={false} showOverrideIndicator={false}