From 8321c06e16a06614f6be142fc0ffa975e3304f72 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 15:10:54 +0100 Subject: [PATCH 1/4] refactor: extract Enhance with AI into shared components Extract all "Enhance with AI" functionality into reusable shared components following DRY principles and clean code guidelines. Changes: - Create shared/enhancement/ folder for related functionality - Extract EnhanceWithAI component (AI enhancement with model override) - Extract EnhancementHistoryButton component (version history UI) - Extract enhancement constants and types - Refactor add-feature-dialog.tsx to use shared components - Refactor edit-feature-dialog.tsx to use shared components - Refactor follow-up-dialog.tsx to use shared components - Add history tracking to add-feature-dialog for consistency Benefits: - Eliminated ~527 lines of duplicated code - Single source of truth for enhancement logic - Consistent UX across all dialogs - Easier maintenance and extensibility - Better code organization Co-Authored-By: Claude Sonnet 4.5 --- apps/ui/src/components/views/board-view.tsx | 4 + .../board-view/dialogs/add-feature-dialog.tsx | 158 +++--------- .../dialogs/edit-feature-dialog.tsx | 239 ++---------------- .../board-view/dialogs/follow-up-dialog.tsx | 52 +++- .../views/board-view/dialogs/index.ts | 2 +- .../board-view/hooks/use-follow-up-state.ts | 16 ++ .../shared/enhancement/enhance-with-ai.tsx | 154 +++++++++++ .../enhancement/enhancement-constants.ts | 20 ++ .../enhancement-history-button.tsx | 129 ++++++++++ .../board-view/shared/enhancement/index.ts | 3 + .../views/board-view/shared/index.ts | 1 + 11 files changed, 441 insertions(+), 337 deletions(-) create mode 100644 apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx create mode 100644 apps/ui/src/components/views/board-view/shared/enhancement/enhancement-constants.ts create mode 100644 apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx create mode 100644 apps/ui/src/components/views/board-view/shared/enhancement/index.ts diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 2b1e3591..0ef50729 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -161,12 +161,14 @@ export function BoardView() { followUpPrompt, followUpImagePaths, followUpPreviewMap, + followUpPromptHistory, setShowFollowUpDialog, setFollowUpFeature, setFollowUpPrompt, setFollowUpImagePaths, setFollowUpPreviewMap, handleFollowUpDialogChange, + addToPromptHistory, } = useFollowUpState(); // Selection mode hook for mass editing @@ -1422,6 +1424,8 @@ export function BoardView() { onPreviewMapChange={setFollowUpPreviewMap} onSend={handleSendFollowUp} isMaximized={isMaximized} + promptHistory={followUpPromptHistory} + onHistoryAdd={addToPromptHistory} /> {/* Backlog Plan Dialog */} diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 4eb09da9..3fd44d9a 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -21,11 +21,9 @@ import { FeatureTextFilePath as DescriptionTextFilePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { Sparkles, ChevronDown, ChevronRight, Play, Cpu, FolderKanban } from 'lucide-react'; +import { Play, Cpu, FolderKanban } from 'lucide-react'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; -import { getElectronAPI } from '@/lib/electron'; import { modelSupportsThinking } from '@/lib/utils'; import { useAppStore, @@ -43,16 +41,12 @@ import { WorkModeSelector, PlanningModeSelect, AncestorContextSection, + EnhanceWithAI, + EnhancementHistoryButton, + type BaseHistoryEntry, } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; -import { ModelOverrideTrigger, useModelOverride } from '@/components/shared'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { getAncestors, @@ -139,11 +133,12 @@ export function AddFeatureDialog({ // UI state const [previewMap, setPreviewMap] = useState(() => new Map()); const [descriptionError, setDescriptionError] = useState(false); - const [isEnhancing, setIsEnhancing] = useState(false); - const [enhancementMode, setEnhancementMode] = useState< - 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' - >('improve'); - const [enhanceOpen, setEnhanceOpen] = useState(false); + + // Description history state + interface DescriptionHistoryEntry extends BaseHistoryEntry { + description: string; + } + const [descriptionHistory, setDescriptionHistory] = useState([]); // Spawn mode state const [ancestors, setAncestors] = useState([]); @@ -152,9 +147,6 @@ export function AddFeatureDialog({ // Get defaults from store const { defaultPlanningMode, defaultRequirePlanApproval } = useAppStore(); - // Enhancement model override - const enhancementOverride = useModelOverride({ phase: 'enhancementModel' }); - // Track previous open state to detect when dialog opens const wasOpenRef = useRef(false); @@ -171,6 +163,9 @@ export function AddFeatureDialog({ setRequirePlanApproval(defaultRequirePlanApproval); setModelEntry({ model: 'opus' }); + // Initialize description history (empty for new feature) + setDescriptionHistory([]); + // Initialize ancestors for spawn mode if (parentFeature) { const ancestorList = getAncestors(parentFeature, allFeatures); @@ -279,7 +274,7 @@ export function AddFeatureDialog({ setRequirePlanApproval(defaultRequirePlanApproval); setPreviewMap(new Map()); setDescriptionError(false); - setEnhanceOpen(false); + setDescriptionHistory([]); onOpenChange(false); }; @@ -302,33 +297,6 @@ export function AddFeatureDialog({ } }; - const handleEnhanceDescription = async () => { - if (!description.trim() || isEnhancing) return; - - setIsEnhancing(true); - try { - const api = getElectronAPI(); - const result = await api.enhancePrompt?.enhance( - description, - enhancementMode, - enhancementOverride.effectiveModel, - enhancementOverride.effectiveModelEntry.thinkingLevel - ); - - if (result?.success && result.enhancedText) { - setDescription(result.enhancedText); - toast.success('Description enhanced!'); - } else { - toast.error(result?.error || 'Failed to enhance description'); - } - } catch (error) { - logger.error('Enhancement failed:', error); - toast.error('Failed to enhance description'); - } finally { - setIsEnhancing(false); - } - }; - // Shared card styling const cardClass = 'rounded-lg border border-border/50 bg-muted/30 p-4 space-y-3'; const sectionHeaderClass = 'flex items-center gap-2 text-sm font-medium text-foreground'; @@ -380,7 +348,18 @@ export function AddFeatureDialog({ {/* Task Details Section */}
- +
+ + {/* Version History Button */} + entry.description} + title="Version History" + restoreMessage="Description restored from history" + /> +
{ @@ -409,75 +388,22 @@ export function AddFeatureDialog({ />
- {/* Collapsible Enhancement Section */} - - - - - -
- - - - - - setEnhancementMode('improve')}> - Improve Clarity - - setEnhancementMode('technical')}> - Add Technical Details - - setEnhancementMode('simplify')}> - Simplify - - setEnhancementMode('acceptance')}> - Add Acceptance Criteria - - setEnhancementMode('ux-reviewer')}> - User Experience - - - - - - - -
-
-
+ {/* Enhancement Section */} + { + setDescriptionHistory((prev) => [ + ...prev, + { + description: enhancedText, + timestamp: new Date().toISOString(), + source: 'enhance', + enhancementMode: mode, + }, + ]); + }} + />
{/* AI & Execution Section */} diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index ce9ced52..212dea2e 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -21,18 +21,8 @@ import { FeatureTextFilePath as DescriptionTextFilePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { - Sparkles, - ChevronDown, - ChevronRight, - GitBranch, - History, - Cpu, - FolderKanban, -} from 'lucide-react'; +import { GitBranch, Cpu, FolderKanban } from 'lucide-react'; import { toast } from 'sonner'; -import { getElectronAPI } from '@/lib/electron'; import { cn, modelSupportsThinking } from '@/lib/utils'; import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store'; import type { ReasoningEffort, PhaseModelEntry, DescriptionHistoryEntry } from '@automaker/types'; @@ -41,17 +31,11 @@ import { PrioritySelector, WorkModeSelector, PlanningModeSelect, + EnhanceWithAI, + EnhancementHistoryButton, } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; -import { ModelOverrideTrigger, useModelOverride } from '@/components/shared'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { DependencyTreeDialog } from './dependency-tree-dialog'; import { isClaudeModel, supportsReasoningEffort } from '@automaker/types'; @@ -110,11 +94,6 @@ export function EditFeatureDialog({ const [editFeaturePreviewMap, setEditFeaturePreviewMap] = useState( () => new Map() ); - const [isEnhancing, setIsEnhancing] = useState(false); - const [enhancementMode, setEnhancementMode] = useState< - 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' - >('improve'); - const [enhanceOpen, setEnhanceOpen] = useState(false); const [showDependencyTree, setShowDependencyTree] = useState(false); const [planningMode, setPlanningMode] = useState(feature?.planningMode ?? 'skip'); const [requirePlanApproval, setRequirePlanApproval] = useState( @@ -137,11 +116,6 @@ export function EditFeatureDialog({ >(null); // Track the original description when the dialog opened for comparison const [originalDescription, setOriginalDescription] = useState(feature?.description ?? ''); - // Track if history dropdown is open - const [showHistory, setShowHistory] = useState(false); - - // Enhancement model override - const enhancementOverride = useModelOverride({ phase: 'enhancementModel' }); useEffect(() => { setEditingFeature(feature); @@ -153,8 +127,6 @@ export function EditFeatureDialog({ // Reset history tracking state setOriginalDescription(feature.description ?? ''); setDescriptionChangeSource(null); - setShowHistory(false); - setEnhanceOpen(false); // Reset model entry setModelEntry({ model: (feature.model as ModelAlias) || 'opus', @@ -164,7 +136,6 @@ export function EditFeatureDialog({ } else { setEditFeaturePreviewMap(new Map()); setDescriptionChangeSource(null); - setShowHistory(false); } }, [feature]); @@ -237,36 +208,6 @@ export function EditFeatureDialog({ } }; - const handleEnhanceDescription = async () => { - if (!editingFeature?.description.trim() || isEnhancing) return; - - setIsEnhancing(true); - try { - const api = getElectronAPI(); - const result = await api.enhancePrompt?.enhance( - editingFeature.description, - enhancementMode, - enhancementOverride.effectiveModel, // API accepts string, extract from PhaseModelEntry - enhancementOverride.effectiveModelEntry.thinkingLevel // Pass thinking level - ); - - if (result?.success && result.enhancedText) { - const enhancedText = result.enhancedText; - setEditingFeature((prev) => (prev ? { ...prev, description: enhancedText } : prev)); - // Track that this change was from enhancement - setDescriptionChangeSource({ source: 'enhance', mode: enhancementMode }); - toast.success('Description enhanced!'); - } else { - toast.error(result?.error || 'Failed to enhance description'); - } - } catch (error) { - logger.error('Enhancement failed:', error); - toast.error('Failed to enhance description'); - } finally { - setIsEnhancing(false); - } - }; - if (!editingFeature) { return null; } @@ -306,92 +247,17 @@ export function EditFeatureDialog({ {/* Version History Button */} {feature?.descriptionHistory && feature.descriptionHistory.length > 0 && ( - - - - - -
-

Version History

-

- Click a version to restore it -

-
-
- {[...(feature.descriptionHistory || [])] - .reverse() - .map((entry: DescriptionHistoryEntry, index: number) => { - const isCurrentVersion = - entry.description === editingFeature.description; - const date = new Date(entry.timestamp); - const formattedDate = date.toLocaleDateString(undefined, { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - const getEnhancementModeLabel = (mode?: string) => { - const labels: Record = { - improve: 'Improve Clarity', - technical: 'Add Technical Details', - simplify: 'Simplify', - acceptance: 'Add Acceptance Criteria', - 'ux-reviewer': 'User Experience', - }; - return labels[mode || 'improve'] || mode || 'improve'; - }; - const sourceLabel = - entry.source === 'initial' - ? 'Original' - : entry.source === 'enhance' - ? `Enhanced (${getEnhancementModeLabel(entry.enhancementMode)})` - : 'Edited'; - - return ( - - ); - })} -
-
-
+ { + setEditingFeature((prev) => (prev ? { ...prev, description } : prev)); + setDescriptionChangeSource('edit'); + }} + valueAccessor={(entry) => entry.description} + title="Version History" + restoreMessage="Description restored from history" + /> )} - {/* Collapsible Enhancement Section */} - - - - - -
- - - - - - setEnhancementMode('improve')}> - Improve Clarity - - setEnhancementMode('technical')}> - Add Technical Details - - setEnhancementMode('simplify')}> - Simplify - - setEnhancementMode('acceptance')}> - Add Acceptance Criteria - - setEnhancementMode('ux-reviewer')}> - User Experience - - - - - - - -
-
-
+ {/* Enhancement Section */} + + setEditingFeature((prev) => (prev ? { ...prev, description: enhanced } : prev)) + } + onHistoryAdd={({ mode }) => setDescriptionChangeSource({ source: 'enhance', mode })} + /> {/* AI & Execution Section */} diff --git a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx index 12929985..23de2a26 100644 --- a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -17,6 +18,19 @@ import { } from '@/components/ui/description-image-dropzone'; import { MessageSquare } from 'lucide-react'; import { Feature } from '@/store/app-store'; +import { EnhanceWithAI, EnhancementHistoryButton, type EnhancementMode } from '../shared'; + +const logger = createLogger('FollowUpDialog'); + +/** + * A single entry in the follow-up prompt history + */ +export interface FollowUpHistoryEntry { + prompt: string; + timestamp: string; // ISO date string + source: 'initial' | 'enhance' | 'edit'; + enhancementMode?: EnhancementMode; +} interface FollowUpDialogProps { open: boolean; @@ -30,6 +44,10 @@ interface FollowUpDialogProps { onPreviewMapChange: (map: ImagePreviewMap) => void; onSend: () => void; isMaximized: boolean; + /** History of prompt versions for restoration */ + promptHistory?: FollowUpHistoryEntry[]; + /** Callback to add a new entry to prompt history */ + onHistoryAdd?: (entry: FollowUpHistoryEntry) => void; } export function FollowUpDialog({ @@ -44,9 +62,11 @@ export function FollowUpDialog({ onPreviewMapChange, onSend, isMaximized, + promptHistory = [], + onHistoryAdd, }: FollowUpDialogProps) { - const handleClose = (open: boolean) => { - if (!open) { + const handleClose = (openState: boolean) => { + if (!openState) { onOpenChange(false); } }; @@ -77,7 +97,18 @@ export function FollowUpDialog({
- +
+ + {/* Version History Button */} + entry.prompt} + title="Prompt History" + restoreMessage="Prompt restored from history" + /> +
+ + {/* Enhancement Section */} + { + onHistoryAdd?.({ + prompt: enhancedText, + timestamp: new Date().toISOString(), + source: 'enhance', + enhancementMode: mode, + }); + }} + /> +

The agent will continue from where it left off, using the existing context. You can attach screenshots to help explain the issue. diff --git a/apps/ui/src/components/views/board-view/dialogs/index.ts b/apps/ui/src/components/views/board-view/dialogs/index.ts index b8d5aa30..659f4d7e 100644 --- a/apps/ui/src/components/views/board-view/dialogs/index.ts +++ b/apps/ui/src/components/views/board-view/dialogs/index.ts @@ -5,6 +5,6 @@ export { CompletedFeaturesModal } from './completed-features-modal'; export { ArchiveAllVerifiedDialog } from './archive-all-verified-dialog'; export { DeleteCompletedFeatureDialog } from './delete-completed-feature-dialog'; export { EditFeatureDialog } from './edit-feature-dialog'; -export { FollowUpDialog } from './follow-up-dialog'; +export { FollowUpDialog, type FollowUpHistoryEntry } from './follow-up-dialog'; export { PlanApprovalDialog } from './plan-approval-dialog'; export { MassEditDialog } from './mass-edit-dialog'; diff --git a/apps/ui/src/components/views/board-view/hooks/use-follow-up-state.ts b/apps/ui/src/components/views/board-view/hooks/use-follow-up-state.ts index f5548b9c..5dd972b9 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-follow-up-state.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-follow-up-state.ts @@ -4,13 +4,18 @@ import { FeatureImagePath as DescriptionImagePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; +import type { FollowUpHistoryEntry } from '../dialogs/follow-up-dialog'; +/** + * Custom hook for managing follow-up dialog state including prompt history + */ export function useFollowUpState() { const [showFollowUpDialog, setShowFollowUpDialog] = useState(false); const [followUpFeature, setFollowUpFeature] = useState(null); const [followUpPrompt, setFollowUpPrompt] = useState(''); const [followUpImagePaths, setFollowUpImagePaths] = useState([]); const [followUpPreviewMap, setFollowUpPreviewMap] = useState(() => new Map()); + const [followUpPromptHistory, setFollowUpPromptHistory] = useState([]); const resetFollowUpState = useCallback(() => { setShowFollowUpDialog(false); @@ -18,6 +23,7 @@ export function useFollowUpState() { setFollowUpPrompt(''); setFollowUpImagePaths([]); setFollowUpPreviewMap(new Map()); + setFollowUpPromptHistory([]); }, []); const handleFollowUpDialogChange = useCallback( @@ -31,6 +37,13 @@ export function useFollowUpState() { [resetFollowUpState] ); + /** + * Adds a new entry to the prompt history + */ + const addToPromptHistory = useCallback((entry: FollowUpHistoryEntry) => { + setFollowUpPromptHistory((prev) => [...prev, entry]); + }, []); + return { // State showFollowUpDialog, @@ -38,14 +51,17 @@ export function useFollowUpState() { followUpPrompt, followUpImagePaths, followUpPreviewMap, + followUpPromptHistory, // Setters setShowFollowUpDialog, setFollowUpFeature, setFollowUpPrompt, setFollowUpImagePaths, setFollowUpPreviewMap, + setFollowUpPromptHistory, // Helpers resetFollowUpState, handleFollowUpDialogChange, + addToPromptHistory, }; } diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx new file mode 100644 index 00000000..d08d2c04 --- /dev/null +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx @@ -0,0 +1,154 @@ +import { useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; +import { Button } from '@/components/ui/button'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Sparkles, ChevronDown, ChevronRight } from 'lucide-react'; +import { toast } from 'sonner'; +import { getElectronAPI } from '@/lib/electron'; +import { ModelOverrideTrigger, useModelOverride } from '@/components/shared'; +import { EnhancementMode, ENHANCEMENT_MODE_LABELS } from './enhancement-constants'; + +const logger = createLogger('EnhanceWithAI'); + +interface EnhanceWithAIProps { + /** Current text value to enhance */ + value: string; + /** Callback when text is enhanced */ + onChange: (enhancedText: string) => void; + /** Optional callback to track enhancement in history */ + onHistoryAdd?: (entry: { mode: EnhancementMode; enhancedText: string }) => void; + /** Disable the enhancement feature */ + disabled?: boolean; + /** Additional CSS classes */ + className?: string; +} + +/** + * Reusable "Enhance with AI" component + * + * Provides AI-powered text enhancement with multiple modes: + * - Improve Clarity + * - Add Technical Details + * - Simplify + * - Add Acceptance Criteria + * - User Experience + * + * Used in Add Feature, Edit Feature, and Follow-Up dialogs. + */ +export function EnhanceWithAI({ + value, + onChange, + onHistoryAdd, + disabled = false, + className, +}: EnhanceWithAIProps) { + const [isEnhancing, setIsEnhancing] = useState(false); + const [enhancementMode, setEnhancementMode] = useState('improve'); + const [enhanceOpen, setEnhanceOpen] = useState(false); + + // Enhancement model override + const enhancementOverride = useModelOverride({ phase: 'enhancementModel' }); + + const handleEnhance = async () => { + if (!value.trim() || isEnhancing || disabled) return; + + setIsEnhancing(true); + try { + const api = getElectronAPI(); + const result = await api.enhancePrompt?.enhance( + value, + enhancementMode, + enhancementOverride.effectiveModel + ); + + if (result?.success && result.enhancedText) { + const enhancedText = result.enhancedText; + onChange(enhancedText); + + // Track in history if callback provided + onHistoryAdd?.({ mode: enhancementMode, enhancedText }); + + toast.success('Enhanced successfully!'); + } else { + toast.error(result?.error || 'Failed to enhance'); + } + } catch (error) { + logger.error('Enhancement failed:', error); + toast.error('Failed to enhance'); + } finally { + setIsEnhancing(false); + } + }; + + return ( + + + + + +

+ + + + + + setEnhancementMode('improve')}> + {ENHANCEMENT_MODE_LABELS.improve} + + setEnhancementMode('technical')}> + {ENHANCEMENT_MODE_LABELS.technical} + + setEnhancementMode('simplify')}> + {ENHANCEMENT_MODE_LABELS.simplify} + + setEnhancementMode('acceptance')}> + {ENHANCEMENT_MODE_LABELS.acceptance} + + setEnhancementMode('ux-reviewer')}> + {ENHANCEMENT_MODE_LABELS['ux-reviewer']} + + + + + + + +
+ + + ); +} diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-constants.ts b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-constants.ts new file mode 100644 index 00000000..7338ea8b --- /dev/null +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-constants.ts @@ -0,0 +1,20 @@ +/** Enhancement mode options for AI-powered prompt improvement */ +export type EnhancementMode = 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'; + +/** Labels for enhancement modes displayed in the UI */ +export const ENHANCEMENT_MODE_LABELS: Record = { + improve: 'Improve Clarity', + technical: 'Add Technical Details', + simplify: 'Simplify', + acceptance: 'Add Acceptance Criteria', + 'ux-reviewer': 'User Experience', +}; + +/** Descriptions for enhancement modes (for tooltips/accessibility) */ +export const ENHANCEMENT_MODE_DESCRIPTIONS: Record = { + improve: 'Make the prompt clearer and more concise', + technical: 'Add implementation details and specifications', + simplify: 'Reduce complexity while keeping the core intent', + acceptance: 'Add specific acceptance criteria and test cases', + 'ux-reviewer': 'Add user experience considerations and flows', +}; diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx new file mode 100644 index 00000000..58ab35ac --- /dev/null +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { History } from 'lucide-react'; +import { toast } from 'sonner'; +import { EnhancementMode, ENHANCEMENT_MODE_LABELS } from './enhancement-constants'; + +/** + * Base interface for history entries + */ +export interface BaseHistoryEntry { + timestamp: string; + source: 'initial' | 'enhance' | 'edit'; + enhancementMode?: EnhancementMode; +} + +interface EnhancementHistoryButtonProps { + /** Array of history entries */ + history: T[]; + /** Current value to compare against for highlighting */ + currentValue: string; + /** Callback when a history entry is restored */ + onRestore: (value: string) => void; + /** Function to extract the text value from a history entry */ + valueAccessor: (entry: T) => string; + /** Title for the history popover (e.g., "Version History", "Prompt History") */ + title?: string; + /** Message shown when restoring an entry */ + restoreMessage?: string; +} + +/** + * Reusable history button component for enhancement-related history + * + * Displays a popover with a list of historical versions that can be restored. + * Used in edit-feature-dialog and follow-up-dialog for description/prompt history. + */ +export function EnhancementHistoryButton({ + history, + currentValue, + onRestore, + valueAccessor, + title = 'Version History', + restoreMessage = 'Restored from history', +}: EnhancementHistoryButtonProps) { + const [showHistory, setShowHistory] = useState(false); + + if (history.length === 0) { + return null; + } + + const getSourceLabel = (entry: T): string => { + if (entry.source === 'initial') { + return 'Original'; + } + if (entry.source === 'enhance') { + return `Enhanced (${ENHANCEMENT_MODE_LABELS[entry.enhancementMode ?? 'improve']})`; + } + return 'Edited'; + }; + + const formatDate = (timestamp: string): string => { + const date = new Date(timestamp); + return date.toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + return ( + + + + + +
+

{title}

+

Click a version to restore it

+
+
+ {[...history].reverse().map((entry, index) => { + const value = valueAccessor(entry); + const isCurrentVersion = value === currentValue; + const sourceLabel = getSourceLabel(entry); + const formattedDate = formatDate(entry.timestamp); + + return ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/index.ts b/apps/ui/src/components/views/board-view/shared/enhancement/index.ts new file mode 100644 index 00000000..80db9beb --- /dev/null +++ b/apps/ui/src/components/views/board-view/shared/enhancement/index.ts @@ -0,0 +1,3 @@ +export * from './enhancement-constants'; +export * from './enhance-with-ai'; +export * from './enhancement-history-button'; diff --git a/apps/ui/src/components/views/board-view/shared/index.ts b/apps/ui/src/components/views/board-view/shared/index.ts index dde8a252..5fe7b4c6 100644 --- a/apps/ui/src/components/views/board-view/shared/index.ts +++ b/apps/ui/src/components/views/board-view/shared/index.ts @@ -10,3 +10,4 @@ export * from './planning-mode-selector'; export * from './planning-mode-select'; export * from './ancestor-context-section'; export * from './work-mode-selector'; +export * from './enhancement'; From 7e5d915b60c3a5b61756d3e3f67c5ddc00c48fa4 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 15:17:46 +0100 Subject: [PATCH 2/4] fix: address PR review feedback from Gemini Code Assist Address three issues identified in code review: 1. Fix missing thinkingLevel parameter (Critical) - Added thinkingLevel parameter to enhance API call - Updated electron.ts type definition to match http-api-client - Fixes functional regression in Claude model enhancement 2. Refactor dropdown menu to use constants dynamically - Changed hardcoded DropdownMenuItem components to dynamic generation - Now iterates over ENHANCEMENT_MODE_LABELS object - Ensures automatic sync when new modes are added - Eliminates manual UI updates for new enhancement modes 3. Optimize array reversal performance - Added useMemo hook to memoize reversed history array - Prevents creating new array on every render - Improves performance with lengthy histories All TypeScript errors resolved. Build verified. Co-Authored-By: Claude Sonnet 4.5 --- .../shared/enhancement/enhance-with-ai.tsx | 25 +++++++------------ .../enhancement-history-button.tsx | 7 ++++-- apps/ui/src/lib/electron.ts | 3 ++- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx index d08d2c04..530b43cd 100644 --- a/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx @@ -64,7 +64,8 @@ export function EnhanceWithAI({ const result = await api.enhancePrompt?.enhance( value, enhancementMode, - enhancementOverride.effectiveModel + enhancementOverride.effectiveModel, + enhancementOverride.effectiveModelEntry.thinkingLevel ); if (result?.success && result.enhancedText) { @@ -108,21 +109,13 @@ export function EnhanceWithAI({ - setEnhancementMode('improve')}> - {ENHANCEMENT_MODE_LABELS.improve} - - setEnhancementMode('technical')}> - {ENHANCEMENT_MODE_LABELS.technical} - - setEnhancementMode('simplify')}> - {ENHANCEMENT_MODE_LABELS.simplify} - - setEnhancementMode('acceptance')}> - {ENHANCEMENT_MODE_LABELS.acceptance} - - setEnhancementMode('ux-reviewer')}> - {ENHANCEMENT_MODE_LABELS['ux-reviewer']} - + {(Object.entries(ENHANCEMENT_MODE_LABELS) as [EnhancementMode, string][]).map( + ([mode, label]) => ( + setEnhancementMode(mode)}> + {label} + + ) + )} diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx index 58ab35ac..f0dbcd86 100644 --- a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { Button } from '@/components/ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { History } from 'lucide-react'; @@ -69,6 +69,9 @@ export function EnhancementHistoryButton({ }); }; + // Memoize reversed history to avoid creating new array on every render + const reversedHistory = useMemo(() => [...history].reverse(), [history]); + return ( @@ -88,7 +91,7 @@ export function EnhancementHistoryButton({

Click a version to restore it

- {[...history].reverse().map((entry, index) => { + {reversedHistory.map((entry, index) => { const value = valueAccessor(entry); const isCurrentVersion = value === currentValue; const sourceLabel = getSourceLabel(entry); diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index d68d00ca..e06ed639 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -612,7 +612,8 @@ export interface ElectronAPI { enhance: ( originalText: string, enhancementMode: string, - model?: string + model?: string, + thinkingLevel?: string ) => Promise<{ success: boolean; enhancedText?: string; From 41a6c7f712b94338c21e7a9bb13bac61ce9d36d0 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 15:27:17 +0100 Subject: [PATCH 3/4] fix: address second round of PR review feedback - Add fallback for unknown enhancement modes in history button to prevent "Enhanced (undefined)" UI bug - Move DescriptionHistoryEntry interface to top level in add-feature-dialog - Import and use EnhancementMode type in edit-feature-dialog to eliminate hardcoded types - Make FollowUpHistoryEntry extend BaseHistoryEntry for consistency Co-Authored-By: Claude Sonnet 4.5 --- .../views/board-view/dialogs/add-feature-dialog.tsx | 10 +++++++--- .../views/board-view/dialogs/edit-feature-dialog.tsx | 5 +++-- .../views/board-view/dialogs/follow-up-dialog.tsx | 12 +++++++----- .../enhancement/enhancement-history-button.tsx | 4 +++- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 3fd44d9a..126fe688 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -91,6 +91,13 @@ interface AddFeatureDialogProps { allFeatures?: Feature[]; } +/** + * A single entry in the description history + */ +interface DescriptionHistoryEntry extends BaseHistoryEntry { + description: string; +} + export function AddFeatureDialog({ open, onOpenChange, @@ -135,9 +142,6 @@ export function AddFeatureDialog({ const [descriptionError, setDescriptionError] = useState(false); // Description history state - interface DescriptionHistoryEntry extends BaseHistoryEntry { - description: string; - } const [descriptionHistory, setDescriptionHistory] = useState([]); // Spawn mode state diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 212dea2e..92d633dc 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -33,6 +33,7 @@ import { PlanningModeSelect, EnhanceWithAI, EnhancementHistoryButton, + type EnhancementMode, } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; @@ -63,7 +64,7 @@ interface EditFeatureDialogProps { requirePlanApproval: boolean; }, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: EnhancementMode ) => void; categorySuggestions: string[]; branchSuggestions: string[]; @@ -112,7 +113,7 @@ export function EditFeatureDialog({ // Track the source of description changes for history const [descriptionChangeSource, setDescriptionChangeSource] = useState< - { source: 'enhance'; mode: 'improve' | 'technical' | 'simplify' | 'acceptance' } | 'edit' | null + { source: 'enhance'; mode: EnhancementMode } | 'edit' | null >(null); // Track the original description when the dialog opened for comparison const [originalDescription, setOriginalDescription] = useState(feature?.description ?? ''); diff --git a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx index 23de2a26..4457b5d0 100644 --- a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx @@ -18,18 +18,20 @@ import { } from '@/components/ui/description-image-dropzone'; import { MessageSquare } from 'lucide-react'; import { Feature } from '@/store/app-store'; -import { EnhanceWithAI, EnhancementHistoryButton, type EnhancementMode } from '../shared'; +import { + EnhanceWithAI, + EnhancementHistoryButton, + type EnhancementMode, + type BaseHistoryEntry, +} from '../shared'; const logger = createLogger('FollowUpDialog'); /** * A single entry in the follow-up prompt history */ -export interface FollowUpHistoryEntry { +export interface FollowUpHistoryEntry extends BaseHistoryEntry { prompt: string; - timestamp: string; // ISO date string - source: 'initial' | 'enhance' | 'edit'; - enhancementMode?: EnhancementMode; } interface FollowUpDialogProps { diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx index f0dbcd86..77dac898 100644 --- a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx @@ -54,7 +54,9 @@ export function EnhancementHistoryButton({ return 'Original'; } if (entry.source === 'enhance') { - return `Enhanced (${ENHANCEMENT_MODE_LABELS[entry.enhancementMode ?? 'improve']})`; + const mode = entry.enhancementMode ?? 'improve'; + const label = ENHANCEMENT_MODE_LABELS[mode as EnhancementMode] ?? mode; + return `Enhanced (${label})`; } return 'Edited'; }; From a4a111fad00cb0110f12773b16d1fa55e3d47831 Mon Sep 17 00:00:00 2001 From: Kacper Date: Sun, 11 Jan 2026 17:19:39 +0100 Subject: [PATCH 4/4] feat: add pre-enhancement description tracking for feature updates - Introduced a new parameter `preEnhancementDescription` to capture the original description before enhancements. - Updated the `update` method in `FeatureLoader` to handle the new parameter and maintain a history of original descriptions. - Enhanced UI components to support tracking and restoring pre-enhancement descriptions across various dialogs. - Improved history management in `AddFeatureDialog`, `EditFeatureDialog`, and `FollowUpDialog` to include original text for better user experience. This change enhances the ability to revert to previous descriptions, improving the overall functionality of the feature enhancement process. --- .../src/routes/features/routes/update.ts | 26 ++++--- apps/server/src/services/feature-loader.ts | 28 ++++++- .../board-view/dialogs/add-feature-dialog.tsx | 27 +++++-- .../dialogs/edit-feature-dialog.tsx | 75 ++++++++++++++----- .../board-view/dialogs/follow-up-dialog.tsx | 16 +++- .../board-view/hooks/use-board-actions.ts | 14 +++- .../board-view/hooks/use-board-persistence.ts | 6 +- .../shared/enhancement/enhance-with-ai.tsx | 11 ++- .../enhancement-history-button.tsx | 8 +- apps/ui/src/lib/electron.ts | 3 +- apps/ui/src/lib/http-api-client.ts | 4 +- 11 files changed, 168 insertions(+), 50 deletions(-) diff --git a/apps/server/src/routes/features/routes/update.ts b/apps/server/src/routes/features/routes/update.ts index 9cb8f25e..1a89cda3 100644 --- a/apps/server/src/routes/features/routes/update.ts +++ b/apps/server/src/routes/features/routes/update.ts @@ -10,14 +10,21 @@ import { getErrorMessage, logError } from '../common.js'; export function createUpdateHandler(featureLoader: FeatureLoader) { return async (req: Request, res: Response): Promise => { try { - const { projectPath, featureId, updates, descriptionHistorySource, enhancementMode } = - req.body as { - projectPath: string; - featureId: string; - updates: Partial; - descriptionHistorySource?: 'enhance' | 'edit'; - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'; - }; + const { + projectPath, + featureId, + updates, + descriptionHistorySource, + enhancementMode, + preEnhancementDescription, + } = req.body as { + projectPath: string; + featureId: string; + updates: Partial; + descriptionHistorySource?: 'enhance' | 'edit'; + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'; + preEnhancementDescription?: string; + }; if (!projectPath || !featureId || !updates) { res.status(400).json({ @@ -32,7 +39,8 @@ export function createUpdateHandler(featureLoader: FeatureLoader) { featureId, updates, descriptionHistorySource, - enhancementMode + enhancementMode, + preEnhancementDescription ); res.json({ success: true, feature: updated }); } catch (error) { diff --git a/apps/server/src/services/feature-loader.ts b/apps/server/src/services/feature-loader.ts index 62570b6b..409abd2a 100644 --- a/apps/server/src/services/feature-loader.ts +++ b/apps/server/src/services/feature-loader.ts @@ -308,13 +308,15 @@ export class FeatureLoader { * @param updates - Partial feature updates * @param descriptionHistorySource - Source of description change ('enhance' or 'edit') * @param enhancementMode - Enhancement mode if source is 'enhance' + * @param preEnhancementDescription - Description before enhancement (for restoring original) */ async update( projectPath: string, featureId: string, updates: Partial, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ): Promise { const feature = await this.get(projectPath, featureId); if (!feature) { @@ -338,9 +340,31 @@ export class FeatureLoader { updates.description !== feature.description && updates.description.trim() ) { + const timestamp = new Date().toISOString(); + + // If this is an enhancement and we have the pre-enhancement description, + // add the original text to history first (so user can restore to it) + if ( + descriptionHistorySource === 'enhance' && + preEnhancementDescription && + preEnhancementDescription.trim() + ) { + // Check if this pre-enhancement text is different from the last history entry + const lastEntry = updatedHistory[updatedHistory.length - 1]; + if (!lastEntry || lastEntry.description !== preEnhancementDescription) { + const preEnhanceEntry: DescriptionHistoryEntry = { + description: preEnhancementDescription, + timestamp, + source: updatedHistory.length === 0 ? 'initial' : 'edit', + }; + updatedHistory = [...updatedHistory, preEnhanceEntry]; + } + } + + // Add the new/enhanced description to history const historyEntry: DescriptionHistoryEntry = { description: updates.description, - timestamp: new Date().toISOString(), + timestamp, source: descriptionHistorySource || 'edit', ...(descriptionHistorySource === 'enhance' && enhancementMode ? { enhancementMode } : {}), }; diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 126fe688..797b64b9 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -396,16 +396,29 @@ export function AddFeatureDialog({ { - setDescriptionHistory((prev) => [ - ...prev, - { + onHistoryAdd={({ mode, originalText, enhancedText }) => { + const timestamp = new Date().toISOString(); + setDescriptionHistory((prev) => { + const newHistory = [...prev]; + // Add original text first (so user can restore to pre-enhancement state) + // Only add if it's different from the last entry to avoid duplicates + const lastEntry = prev[prev.length - 1]; + if (!lastEntry || lastEntry.description !== originalText) { + newHistory.push({ + description: originalText, + timestamp, + source: prev.length === 0 ? 'initial' : 'edit', + }); + } + // Add enhanced text + newHistory.push({ description: enhancedText, - timestamp: new Date().toISOString(), + timestamp, source: 'enhance', enhancementMode: mode, - }, - ]); + }); + return newHistory; + }); }} />
diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 92d633dc..9912201d 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -64,7 +64,8 @@ interface EditFeatureDialogProps { requirePlanApproval: boolean; }, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: EnhancementMode + enhancementMode?: EnhancementMode, + preEnhancementDescription?: string ) => void; categorySuggestions: string[]; branchSuggestions: string[]; @@ -117,6 +118,12 @@ export function EditFeatureDialog({ >(null); // Track the original description when the dialog opened for comparison const [originalDescription, setOriginalDescription] = useState(feature?.description ?? ''); + // Track the description before enhancement (so it can be restored) + const [preEnhancementDescription, setPreEnhancementDescription] = useState(null); + // Local history state for real-time display (combines persisted + session history) + const [localHistory, setLocalHistory] = useState( + feature?.descriptionHistory ?? [] + ); useEffect(() => { setEditingFeature(feature); @@ -128,6 +135,8 @@ export function EditFeatureDialog({ // Reset history tracking state setOriginalDescription(feature.description ?? ''); setDescriptionChangeSource(null); + setPreEnhancementDescription(null); + setLocalHistory(feature.descriptionHistory ?? []); // Reset model entry setModelEntry({ model: (feature.model as ModelAlias) || 'opus', @@ -137,6 +146,8 @@ export function EditFeatureDialog({ } else { setEditFeaturePreviewMap(new Map()); setDescriptionChangeSource(null); + setPreEnhancementDescription(null); + setLocalHistory([]); } }, [feature]); @@ -198,7 +209,13 @@ export function EditFeatureDialog({ } } - onUpdate(editingFeature.id, updates, historySource, historyEnhancementMode); + onUpdate( + editingFeature.id, + updates, + historySource, + historyEnhancementMode, + preEnhancementDescription ?? undefined + ); setEditFeaturePreviewMap(new Map()); onClose(); }; @@ -246,20 +263,18 @@ export function EditFeatureDialog({
- {/* Version History Button */} - {feature?.descriptionHistory && feature.descriptionHistory.length > 0 && ( - { - setEditingFeature((prev) => (prev ? { ...prev, description } : prev)); - setDescriptionChangeSource('edit'); - }} - valueAccessor={(entry) => entry.description} - title="Version History" - restoreMessage="Description restored from history" - /> - )} + {/* Version History Button - uses local history for real-time updates */} + { + setEditingFeature((prev) => (prev ? { ...prev, description } : prev)); + setDescriptionChangeSource('edit'); + }} + valueAccessor={(entry) => entry.description} + title="Version History" + restoreMessage="Description restored from history" + />
setEditingFeature((prev) => (prev ? { ...prev, description: enhanced } : prev)) } - onHistoryAdd={({ mode }) => setDescriptionChangeSource({ source: 'enhance', mode })} + onHistoryAdd={({ mode, originalText, enhancedText }) => { + setDescriptionChangeSource({ source: 'enhance', mode }); + setPreEnhancementDescription(originalText); + + // Update local history for real-time display + const timestamp = new Date().toISOString(); + setLocalHistory((prev) => { + const newHistory = [...prev]; + // Add original text first (so user can restore to pre-enhancement state) + const lastEntry = prev[prev.length - 1]; + if (!lastEntry || lastEntry.description !== originalText) { + newHistory.push({ + description: originalText, + timestamp, + source: prev.length === 0 ? 'initial' : 'edit', + }); + } + // Add enhanced text + newHistory.push({ + description: enhancedText, + timestamp, + source: 'enhance', + enhancementMode: mode, + }); + return newHistory; + }); + }} />
diff --git a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx index 4457b5d0..6df1eea0 100644 --- a/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/follow-up-dialog.tsx @@ -126,10 +126,22 @@ export function FollowUpDialog({ { + onHistoryAdd={({ mode, originalText, enhancedText }) => { + const timestamp = new Date().toISOString(); + // Add original text first (so user can restore to pre-enhancement state) + // Only add if it's different from the last history entry + const lastEntry = promptHistory[promptHistory.length - 1]; + if (!lastEntry || lastEntry.prompt !== originalText) { + onHistoryAdd?.({ + prompt: originalText, + timestamp, + source: promptHistory.length === 0 ? 'initial' : 'edit', + }); + } + // Add enhanced text onHistoryAdd?.({ prompt: enhancedText, - timestamp: new Date().toISOString(), + timestamp, source: 'enhance', enhancementMode: mode, }); diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts index 8efacd8b..e25d963e 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts @@ -30,7 +30,8 @@ interface UseBoardActionsProps { featureId: string, updates: Partial, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ) => Promise; persistFeatureDelete: (featureId: string) => Promise; saveCategory: (category: string) => Promise; @@ -251,7 +252,8 @@ export function useBoardActions({ workMode?: 'current' | 'auto' | 'custom'; }, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ) => { const workMode = updates.workMode || 'current'; @@ -308,7 +310,13 @@ export function useBoardActions({ }; updateFeature(featureId, finalUpdates); - persistFeatureUpdate(featureId, finalUpdates, descriptionHistorySource, enhancementMode); + persistFeatureUpdate( + featureId, + finalUpdates, + descriptionHistorySource, + enhancementMode, + preEnhancementDescription + ); if (updates.category) { saveCategory(updates.category); } diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts index df281a56..3c860251 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts @@ -19,7 +19,8 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps featureId: string, updates: Partial, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ) => { if (!currentProject) return; @@ -35,7 +36,8 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps featureId, updates, descriptionHistorySource, - enhancementMode + enhancementMode, + preEnhancementDescription ); if (result.success && result.feature) { updateFeature(result.feature.id, result.feature); diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx index 530b43cd..63b9dedc 100644 --- a/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhance-with-ai.tsx @@ -22,7 +22,11 @@ interface EnhanceWithAIProps { /** Callback when text is enhanced */ onChange: (enhancedText: string) => void; /** Optional callback to track enhancement in history */ - onHistoryAdd?: (entry: { mode: EnhancementMode; enhancedText: string }) => void; + onHistoryAdd?: (entry: { + mode: EnhancementMode; + originalText: string; + enhancedText: string; + }) => void; /** Disable the enhancement feature */ disabled?: boolean; /** Additional CSS classes */ @@ -69,11 +73,12 @@ export function EnhanceWithAI({ ); if (result?.success && result.enhancedText) { + const originalText = value; const enhancedText = result.enhancedText; onChange(enhancedText); - // Track in history if callback provided - onHistoryAdd?.({ mode: enhancementMode, enhancedText }); + // Track in history if callback provided (includes original for restoration) + onHistoryAdd?.({ mode: enhancementMode, originalText, enhancedText }); toast.success('Enhanced successfully!'); } else { diff --git a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx index 77dac898..bf31fde7 100644 --- a/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx +++ b/apps/ui/src/components/views/board-view/shared/enhancement/enhancement-history-button.tsx @@ -45,6 +45,11 @@ export function EnhancementHistoryButton({ }: EnhancementHistoryButtonProps) { const [showHistory, setShowHistory] = useState(false); + // Memoize reversed history to avoid creating new array on every render + // NOTE: This hook MUST be called before any early returns to follow Rules of Hooks + const reversedHistory = useMemo(() => [...history].reverse(), [history]); + + // Early return AFTER all hooks are called if (history.length === 0) { return null; } @@ -71,9 +76,6 @@ export function EnhancementHistoryButton({ }); }; - // Memoize reversed history to avoid creating new array on every render - const reversedHistory = useMemo(() => [...history].reverse(), [history]); - return ( diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index e06ed639..3abe43fa 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -462,7 +462,8 @@ export interface FeaturesAPI { featureId: string, updates: Partial, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ) => Promise<{ success: boolean; feature?: Feature; error?: string }>; delete: (projectPath: string, featureId: string) => Promise<{ success: boolean; error?: string }>; getAgentOutput: ( diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 7d442836..c64c427e 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -1459,7 +1459,8 @@ export class HttpApiClient implements ElectronAPI { featureId: string, updates: Partial, descriptionHistorySource?: 'enhance' | 'edit', - enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer' + enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer', + preEnhancementDescription?: string ) => this.post('/api/features/update', { projectPath, @@ -1467,6 +1468,7 @@ export class HttpApiClient implements ElectronAPI { updates, descriptionHistorySource, enhancementMode, + preEnhancementDescription, }), delete: (projectPath: string, featureId: string) => this.post('/api/features/delete', { projectPath, featureId }),