Browse Source

fix: gallery refs alignment on delete and tutorial timeout cleanup

- OutputGalleryNode: splice imageRefs/videoRefs alongside images/videos
  in removeMedia to keep arrays in lockstep
- TutorialOverlay: track and clean up actionCompleted advance timeout
  via advanceTimeoutRef to prevent stale nextTutorialStep after skip
- TutorialOverlay: track and clean up populate-content nested timeouts
  via populateTimeoutIds ref to prevent post-teardown state mutations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 3 months ago
parent
commit
f6b3193e42
  1. 16
      src/components/nodes/OutputGalleryNode.tsx
  2. 28
      src/components/onboarding/TutorialOverlay.tsx

16
src/components/nodes/OutputGalleryNode.tsx

@ -79,14 +79,22 @@ export function OutputGalleryNode({ id, data, selected }: NodeProps<OutputGaller
if (item.type === "image") {
const images = [...(nodeData.images || [])];
const imageRefs = [...(nodeData.imageRefs || [])];
const imgIndex = images.indexOf(item.src);
if (imgIndex !== -1) images.splice(imgIndex, 1);
updateNodeData(id, { images });
if (imgIndex !== -1) {
images.splice(imgIndex, 1);
if (imgIndex < imageRefs.length) imageRefs.splice(imgIndex, 1);
}
updateNodeData(id, { images, imageRefs });
} else {
const videos = [...(nodeData.videos || [])];
const videoRefs = [...(nodeData.videoRefs || [])];
const vidIndex = videos.indexOf(item.src);
if (vidIndex !== -1) videos.splice(vidIndex, 1);
updateNodeData(id, { videos });
if (vidIndex !== -1) {
videos.splice(vidIndex, 1);
if (vidIndex < videoRefs.length) videoRefs.splice(vidIndex, 1);
}
updateNodeData(id, { videos, videoRefs });
}
// Adjust lightbox after removal

28
src/components/onboarding/TutorialOverlay.tsx

@ -18,6 +18,8 @@ export function TutorialOverlay() {
const nodesPopulated = useRef(false);
const demonstrateNodesAdded = useRef(false);
const demonstrateTimeoutIds = useRef<ReturnType<typeof setTimeout>[]>([]);
const advanceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const populateTimeoutIds = useRef<ReturnType<typeof setTimeout>[]>([]);
const tutorialActive = useFTUXStore((state) => state.tutorialActive);
const currentTutorialStep = useFTUXStore((state) => state.currentTutorialStep);
@ -113,10 +115,19 @@ export function TutorialOverlay() {
completeCurrentStep();
// Advance to next step after configurable delay (default 1000ms)
const delay = currentStep.advanceDelay !== undefined ? currentStep.advanceDelay : 1000;
setTimeout(() => {
if (advanceTimeoutRef.current) clearTimeout(advanceTimeoutRef.current);
advanceTimeoutRef.current = setTimeout(() => {
advanceTimeoutRef.current = null;
nextTutorialStep();
}, delay);
}
return () => {
if (advanceTimeoutRef.current) {
clearTimeout(advanceTimeoutRef.current);
advanceTimeoutRef.current = null;
}
};
}, [
tutorialActive,
currentTutorialStep,
@ -162,9 +173,11 @@ export function TutorialOverlay() {
// Check if we're on the "populate-content" step
if (currentStep?.id === "populate-content" && !currentStep.completed) {
nodesPopulated.current = true;
const timeoutIds = populateTimeoutIds.current;
timeoutIds.length = 0;
// Wait a bit before populating to show the message first
setTimeout(() => {
timeoutIds.push(setTimeout(() => {
// Find the image input and prompt nodes
const imageInputNode = nodes.find((node) => node.type === "imageInput");
const promptNode = nodes.find((node) => node.type === "prompt");
@ -185,11 +198,16 @@ export function TutorialOverlay() {
}
// Auto-advance after populating
setTimeout(() => {
timeoutIds.push(setTimeout(() => {
completeCurrentStep();
nextTutorialStep();
}, 1500);
}, 1000);
}, 1500));
}, 1000));
return () => {
timeoutIds.forEach(clearTimeout);
timeoutIds.length = 0;
};
}
// Reset ref when tutorial ends

Loading…
Cancel
Save