From c8f87d0dbb84c78eb2a538c3e2613dd695b19b8c Mon Sep 17 00:00:00 2001 From: Kacper Date: Wed, 10 Dec 2025 11:38:27 +0100 Subject: [PATCH] fix(kanban): persist image previews when switching tabs in Add Feature modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lifts image preview state up to parent components to prevent loss of preview thumbnails when switching between tabs. Added previewMap/onPreviewMapChange props to DescriptionImageDropZone for parent-controlled state management. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4 --- .automaker/feature_list.json | 32 +++++++++++++++++++ .../ui/description-image-dropzone.tsx | 31 ++++++++++++++++-- app/src/components/views/board-view.tsx | 25 ++++++++++++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json index faaecc9c..e9c3df86 100644 --- a/.automaker/feature_list.json +++ b/.automaker/feature_list.json @@ -205,5 +205,37 @@ "skipTests": true, "model": "sonnet", "thinkingLevel": "none" + }, + { + "id": "feature-1765360665626-u2vhr80aa", + "category": "Uncategorized", + "description": "I dont want you to change any code describe what u see in attached image", + "steps": [], + "status": "backlog", + "startedAt": "2025-12-10T09:57:47.494Z", + "imagePaths": [ + { + "id": "img-1765360662146-d5qi79j88", + "path": "/Users/shirone/Library/Application Support/automaker/images/1765360662144-2pt9bt1u4_image-test.png", + "filename": "image-test.png", + "mimeType": "image/png" + } + ], + "skipTests": true, + "model": "gpt-5.1-codex", + "thinkingLevel": "none" + }, + { + "id": "feature-1765360739103-3h218d1nn", + "category": "Kanban", + "description": "When u write new feature for ai agent and attacht context images and change tab to choose diff model and go back to prompt tab the image preview break and im not sure if it even saved properly in state to be later attached check it out for me", + "steps": [], + "status": "waiting_approval", + "startedAt": "2025-12-10T09:59:02.988Z", + "imagePaths": [], + "skipTests": true, + "summary": "Fixed image preview breaking when switching tabs in Add Feature modal. Added previewMap/onPreviewMapChange props to DescriptionImageDropZone component to lift preview state up to parent. Modified: description-image-dropzone.tsx (added parent-controlled state support), board-view.tsx (added newFeaturePreviewMap and followUpPreviewMap state, wired up to DescriptionImageDropZone). Image paths were already stored correctly in state - only the preview thumbnails (base64) were lost on tab switch due to component unmounting.", + "model": "opus", + "thinkingLevel": "high" } ] \ No newline at end of file diff --git a/app/src/components/ui/description-image-dropzone.tsx b/app/src/components/ui/description-image-dropzone.tsx index 17e73bb8..76874a41 100644 --- a/app/src/components/ui/description-image-dropzone.tsx +++ b/app/src/components/ui/description-image-dropzone.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useRef, useCallback } from "react"; +import React, { useState, useRef, useCallback, useEffect } from "react"; import { cn } from "@/lib/utils"; import { ImageIcon, X, Loader2 } from "lucide-react"; import { Textarea } from "@/components/ui/textarea"; @@ -14,6 +14,9 @@ export interface FeatureImagePath { mimeType: string; } +// Map to store preview data by image ID (persisted across component re-mounts) +export type ImagePreviewMap = Map; + interface DescriptionImageDropZoneProps { value: string; onChange: (value: string) => void; @@ -24,6 +27,9 @@ interface DescriptionImageDropZoneProps { disabled?: boolean; maxFiles?: number; maxFileSize?: number; // in bytes, default 10MB + // Optional: pass preview map from parent to persist across tab switches + previewMap?: ImagePreviewMap; + onPreviewMapChange?: (map: ImagePreviewMap) => void; } const ACCEPTED_IMAGE_TYPES = [ @@ -45,12 +51,31 @@ export function DescriptionImageDropZone({ disabled = false, maxFiles = 5, maxFileSize = DEFAULT_MAX_FILE_SIZE, + previewMap, + onPreviewMapChange, }: DescriptionImageDropZoneProps) { const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); - const [previewImages, setPreviewImages] = useState>( - new Map() + // Use parent-provided preview map if available, otherwise use local state + const [localPreviewImages, setLocalPreviewImages] = useState>( + () => new Map() ); + + // Determine which preview map to use - prefer parent-controlled state + const previewImages = previewMap !== undefined ? previewMap : localPreviewImages; + const setPreviewImages = useCallback((updater: Map | ((prev: Map) => Map)) => { + if (onPreviewMapChange) { + const currentMap = previewMap !== undefined ? previewMap : localPreviewImages; + const newMap = typeof updater === 'function' ? updater(currentMap) : updater; + onPreviewMapChange(newMap); + } else { + setLocalPreviewImages((prev) => { + const newMap = typeof updater === 'function' ? updater(prev) : updater; + return newMap; + }); + } + }, [onPreviewMapChange, previewMap, localPreviewImages]); + const fileInputRef = useRef(null); const currentProject = useAppStore((state) => state.currentProject); diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx index f1d14f1c..39daac57 100644 --- a/app/src/components/views/board-view.tsx +++ b/app/src/components/views/board-view.tsx @@ -41,6 +41,7 @@ import { FeatureImageUpload } from "@/components/ui/feature-image-upload"; import { DescriptionImageDropZone, FeatureImagePath as DescriptionImagePath, + ImagePreviewMap, } from "@/components/ui/description-image-dropzone"; import { Dialog, @@ -197,6 +198,13 @@ export function BoardView() { const [followUpImagePaths, setFollowUpImagePaths] = useState< DescriptionImagePath[] >([]); + // Preview maps to persist image previews across tab switches + const [newFeaturePreviewMap, setNewFeaturePreviewMap] = useState( + () => new Map() + ); + const [followUpPreviewMap, setFollowUpPreviewMap] = useState( + () => new Map() + ); // Make current project available globally for modal useEffect(() => { @@ -716,6 +724,8 @@ export function BoardView() { model: "opus", thinkingLevel: "none", }); + // Clear the preview map when the feature is added + setNewFeaturePreviewMap(new Map()); setShowAddDialog(false); }; @@ -991,6 +1001,7 @@ export function BoardView() { setFollowUpFeature(null); setFollowUpPrompt(""); setFollowUpImagePaths([]); + setFollowUpPreviewMap(new Map()); // Show success toast immediately toast.success("Follow-up started", { @@ -1485,7 +1496,13 @@ export function BoardView() { {/* Add Feature Dialog */} - + { + setShowAddDialog(open); + // Clear preview map when dialog closes + if (!open) { + setNewFeaturePreviewMap(new Map()); + } + }}>
@@ -2077,6 +2096,7 @@ export function BoardView() { setFollowUpFeature(null); setFollowUpPrompt(""); setFollowUpImagePaths([]); + setFollowUpPreviewMap(new Map()); } }} > @@ -2115,6 +2135,8 @@ export function BoardView() { images={followUpImagePaths} onImagesChange={setFollowUpImagePaths} placeholder="Describe what needs to be fixed or changed..." + previewMap={followUpPreviewMap} + onPreviewMapChange={setFollowUpPreviewMap} />

@@ -2130,6 +2152,7 @@ export function BoardView() { setFollowUpFeature(null); setFollowUpPrompt(""); setFollowUpImagePaths([]); + setFollowUpPreviewMap(new Map()); }} > Cancel