Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Loader2, Save, Settings, Trash2, PaintBucket, Check } from "lucide-react";
import type { Space } from "~/flow/interfaces/sessions/spaces";
Expand All @@ -23,10 +23,17 @@ export function SpaceEditor({ space, onClose, onDelete, onSpacesUpdate }: SpaceE
const [saveSuccess, setSaveSuccess] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
// Tracks the latest saveSuccess value for the auto-close timeout below,
// since a setTimeout closure only ever sees the state at the time it was scheduled.
const saveSuccessRef = useRef(false);

// Update edited space
const updateEditedSpace = (updates: Partial<Space>) => {
setEditedSpace((prev) => ({ ...prev, ...updates }));
// A fresh edit invalidates the previous "Saved" confirmation so the Save
// button becomes clickable again instead of staying stuck on "Saved".
saveSuccessRef.current = false;
setSaveSuccess(false);
};

// Handle space update
Expand Down Expand Up @@ -58,11 +65,12 @@ export function SpaceEditor({ space, onClose, onDelete, onSpacesUpdate }: SpaceE

await flow.spaces.updateSpace(space.profileId, space.id, updatedFields);
onSpacesUpdate(); // Refetch spaces after successful update
saveSuccessRef.current = true;
setSaveSuccess(true);

// Auto-close after short delay
// Auto-close after short delay, unless the user started editing again in the meantime
setTimeout(() => {
if (saveSuccess) {
if (saveSuccessRef.current) {
Comment on lines +68 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not let an older save overwrite a newer edit.

If the user edits the space while flow.spaces.updateSpace(...) is pending, Lines 35 or 109 set saveSuccessRef.current to false. When the request resolves, Line 68 unconditionally sets it back to true, and Lines 72-73 can call onClose() while editedSpace contains unsaved changes.

The shared boolean also does not identify which save owns the timeout. A timeout from an earlier save can observe true from a later save and close the editor too early.

Capture an edit generation when handleSave starts. Increment it in both edit handlers. Apply the success state and close only when the captured generation still matches.

Proposed fix
 const saveSuccessRef = useRef(false);
+const editGenerationRef = useRef(0);

 const updateEditedSpace = (updates: Partial<Space>) => {
+  editGenerationRef.current += 1;
   setEditedSpace((prev) => ({ ...prev, ...updates }));

 const handleSave = async () => {
+  const saveGeneration = editGenerationRef.current;
   setIsSaving(true);
   setSaveSuccess(false);
   try {
     ...
     await flow.spaces.updateSpace(space.profileId, space.id, updatedFields);
     onSpacesUpdate();
+    if (saveGeneration !== editGenerationRef.current) {
+      return;
+    }
     saveSuccessRef.current = true;
     setSaveSuccess(true);

     setTimeout(() => {
-      if (saveSuccessRef.current) {
+      if (saveGeneration === editGenerationRef.current && saveSuccessRef.current) {
         onClose();
       }
     }, 1500);
   }

 const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  editGenerationRef.current += 1;
   setEditedSpace({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/settings/sections/spaces/space-editor.tsx` around
lines 68 - 73, Update handleSave and both edit handlers to use an
edit-generation counter: capture the current generation when handleSave starts,
increment it whenever Lines 35 or 109 process an edit, and only set
saveSuccessRef/current success state or invoke onClose from the save completion
timeout when the captured generation still matches. Ensure each timeout is tied
to its originating save so an older request cannot overwrite newer edits or
close the editor prematurely.

onClose();
}
Comment on lines +73 to 75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Shared ref conflates save generations

When the user saves, edits, and successfully saves again within 1.5 seconds, the first uncancelled timeout reads the shared saveSuccessRef value written by the second save and closes the editor less than 1.5 seconds after the latest save.

}, 1500);
Expand Down Expand Up @@ -97,6 +105,9 @@ export function SpaceEditor({ space, onClose, onDelete, onSpacesUpdate }: SpaceE
...editedSpace,
name: e.target.value
});
// See updateEditedSpace: a fresh edit should re-enable the Save button.
saveSuccessRef.current = false;
setSaveSuccess(false);
};

// Detect if there are unsaved changes
Expand Down