fix(kanban): persist image previews when switching tabs in Add Feature modal

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 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-10 11:38:27 +01:00
parent 18604078a6
commit c8f87d0dbb
3 changed files with 84 additions and 4 deletions

View File

@@ -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"
}
]

View File

@@ -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<string, string>;
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<Map<string, string>>(
new Map()
// Use parent-provided preview map if available, otherwise use local state
const [localPreviewImages, setLocalPreviewImages] = useState<Map<string, string>>(
() => new Map()
);
// Determine which preview map to use - prefer parent-controlled state
const previewImages = previewMap !== undefined ? previewMap : localPreviewImages;
const setPreviewImages = useCallback((updater: Map<string, string> | ((prev: Map<string, string>) => Map<string, string>)) => {
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<HTMLInputElement>(null);
const currentProject = useAppStore((state) => state.currentProject);

View File

@@ -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<ImagePreviewMap>(
() => new Map()
);
const [followUpPreviewMap, setFollowUpPreviewMap] = useState<ImagePreviewMap>(
() => 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() {
</div>
{/* Add Feature Dialog */}
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
<Dialog open={showAddDialog} onOpenChange={(open) => {
setShowAddDialog(open);
// Clear preview map when dialog closes
if (!open) {
setNewFeaturePreviewMap(new Map());
}
}}>
<DialogContent
compact={!isMaximized}
data-testid="add-feature-dialog"
@@ -1536,6 +1553,8 @@ export function BoardView() {
setNewFeature({ ...newFeature, imagePaths: images })
}
placeholder="Describe the feature..."
previewMap={newFeaturePreviewMap}
onPreviewMapChange={setNewFeaturePreviewMap}
/>
</div>
<div className="space-y-2">
@@ -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}
/>
</div>
<p className="text-xs text-muted-foreground">
@@ -2130,6 +2152,7 @@ export function BoardView() {
setFollowUpFeature(null);
setFollowUpPrompt("");
setFollowUpImagePaths([]);
setFollowUpPreviewMap(new Map());
}}
>
Cancel