From 8321c06e16a06614f6be142fc0ffa975e3304f72 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 15:10:54 +0100 Subject: [PATCH] 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';