fix(web): stop autosave loop by ignoring volatile server-echo fields
Server Deploy / deploy (push) Successful in 39s
Server Deploy / deploy (push) Successful in 39s
This commit is contained in:
@@ -35,6 +35,17 @@ function AdhocBadge() {
|
||||
);
|
||||
}
|
||||
|
||||
// Stable snapshot of only the fields the editor controls. Excludes volatile
|
||||
// server-echo fields (e.g. updated_at) that would otherwise change on every
|
||||
// save and cause autosave to loop forever.
|
||||
function snapshotOf(w: Workflow): string {
|
||||
return JSON.stringify({
|
||||
name: w.name,
|
||||
target_server_ids: w.target_server_ids,
|
||||
steps: w.steps,
|
||||
});
|
||||
}
|
||||
|
||||
function timeAgo(date: Date): string {
|
||||
const s = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||
if (s < 5) return "just now";
|
||||
@@ -80,7 +91,7 @@ export default function WorkflowBuilder() {
|
||||
useEffect(() => {
|
||||
if (loaded && !wf) {
|
||||
setWf(loaded);
|
||||
savedSnapshotRef.current = JSON.stringify(loaded);
|
||||
savedSnapshotRef.current = snapshotOf(loaded);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [loaded]);
|
||||
@@ -115,7 +126,7 @@ export default function WorkflowBuilder() {
|
||||
// Must stay above the early return below so hook order is stable.
|
||||
useEffect(() => {
|
||||
if (!wf || savedSnapshotRef.current === null) return;
|
||||
if (JSON.stringify(wf) === savedSnapshotRef.current) return;
|
||||
if (snapshotOf(wf) === savedSnapshotRef.current) return;
|
||||
const t = setTimeout(() => {
|
||||
save();
|
||||
}, 800);
|
||||
@@ -147,7 +158,7 @@ export default function WorkflowBuilder() {
|
||||
if (savingRef.current) return;
|
||||
const current = wfRef.current;
|
||||
if (!current) return;
|
||||
const snapshot = JSON.stringify(current);
|
||||
const snapshot = snapshotOf(current);
|
||||
if (snapshot === savedSnapshotRef.current) return;
|
||||
savingRef.current = true;
|
||||
setSaving(true);
|
||||
@@ -158,10 +169,10 @@ export default function WorkflowBuilder() {
|
||||
setError("Save failed: server returned an unexpected response.");
|
||||
return;
|
||||
}
|
||||
if (JSON.stringify(wfRef.current) === snapshot) {
|
||||
if (wfRef.current && snapshotOf(wfRef.current) === snapshot) {
|
||||
// Nothing changed while the request was in flight: adopt the
|
||||
// server echo as the new saved baseline.
|
||||
savedSnapshotRef.current = JSON.stringify(updated);
|
||||
savedSnapshotRef.current = snapshotOf(updated);
|
||||
setWf(updated);
|
||||
} else {
|
||||
// The user edited again mid-flight. Keep their newer state and
|
||||
@@ -177,7 +188,7 @@ export default function WorkflowBuilder() {
|
||||
setSaving(false);
|
||||
// If edits arrived during the save (or a concurrent save was
|
||||
// skipped), persist them on the next tick.
|
||||
if (wfRef.current && JSON.stringify(wfRef.current) !== savedSnapshotRef.current) {
|
||||
if (wfRef.current && snapshotOf(wfRef.current) !== savedSnapshotRef.current) {
|
||||
setTimeout(() => save(), 0);
|
||||
}
|
||||
}
|
||||
@@ -638,7 +649,7 @@ export default function WorkflowBuilder() {
|
||||
onSaved={(w) => {
|
||||
// The modal already persisted w; sync the snapshot so
|
||||
// autosave doesn't fire a redundant follow-up save.
|
||||
savedSnapshotRef.current = JSON.stringify(w);
|
||||
savedSnapshotRef.current = snapshotOf(w);
|
||||
setWf(w);
|
||||
}}
|
||||
onClose={() => setEditWorkflowOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user