diff --git a/apps/ui/src/components/ui/autocomplete.tsx b/apps/ui/src/components/ui/autocomplete.tsx index c12aa0731..ab18d274a 100644 --- a/apps/ui/src/components/ui/autocomplete.tsx +++ b/apps/ui/src/components/ui/autocomplete.tsx @@ -13,6 +13,9 @@ import { } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +/** + * Option item for the Autocomplete component + */ export interface AutocompleteOption { value: string; label?: string; @@ -44,6 +47,19 @@ function normalizeOption(opt: string | AutocompleteOption): AutocompleteOption { return { ...opt, label: opt.label ?? opt.value }; } +/** + * A generic Autocomplete/Combobox component built on top of shadcn/ui. + * + * Features: + * - Searchable options list + * - Support for creating new values that don't exist in options + * - Custom icons and badges + * - Accessible Popover + Command implementation + * - Responsive width handling + * + * @param props - Component props + * @returns The rendered autocomplete component + */ export function Autocomplete({ value, onChange, @@ -87,9 +103,11 @@ export function Autocomplete({ // Filter options based on input const filteredOptions = React.useMemo(() => { - if (!inputValue) return normalizedOptions; + // Filter out options with undefined/null values + const validOptions = normalizedOptions.filter((opt) => opt.value != null); + if (!inputValue) return validOptions; const lower = inputValue.toLowerCase(); - return normalizedOptions.filter( + return validOptions.filter( (opt) => opt.value.toLowerCase().includes(lower) || opt.label?.toLowerCase().includes(lower) ); }, [normalizedOptions, inputValue]); @@ -98,7 +116,7 @@ export function Autocomplete({ const isNewValue = allowCreate && inputValue.trim() && - !normalizedOptions.some((opt) => opt.value.toLowerCase() === inputValue.toLowerCase()); + !normalizedOptions.some((opt) => opt.value?.toLowerCase() === inputValue.toLowerCase()); // Get display value const displayValue = React.useMemo(() => { @@ -184,7 +202,7 @@ export function Autocomplete({ setInputValue(''); setOpen(false); }} - data-testid={`${itemTestIdPrefix}-${option.value.toLowerCase().replace(/[\s/\\]+/g, '-')}`} + data-testid={`${itemTestIdPrefix}-${(option.value ?? '').toLowerCase().replace(/[\s/\\]+/g, '-')}`} > {Icon && } {option.label} diff --git a/apps/ui/src/components/ui/branch-autocomplete.tsx b/apps/ui/src/components/ui/branch-autocomplete.tsx index 8e95442a7..c6af70622 100644 --- a/apps/ui/src/components/ui/branch-autocomplete.tsx +++ b/apps/ui/src/components/ui/branch-autocomplete.tsx @@ -14,6 +14,18 @@ interface BranchAutocompleteProps { 'data-testid'?: string; } +/** + * A specialized Autocomplete component for selecting git branches. + * + * Features: + * - Always shows 'main' at the top of the list + * - Displays card counts badges for branches if provided + * - Allows creating new branches (via allowCreate prop passed to Autocomplete) + * - Includes a GitBranch icon + * + * @param props - Component props + * @returns The rendered branch selection component + */ export function BranchAutocomplete({ value, onChange, @@ -27,7 +39,9 @@ export function BranchAutocomplete({ }: BranchAutocompleteProps) { // Always include "main" at the top of suggestions const branchOptions: AutocompleteOption[] = React.useMemo(() => { - const branchSet = new Set(['main', ...branches]); + // Filter out undefined/null branches + const validBranches = branches.filter((b): b is string => b != null && b !== ''); + const branchSet = new Set(['main', ...validBranches]); return Array.from(branchSet).map((branch) => { const cardCount = branchCardCounts?.[branch]; // Show card count if available, otherwise show "default" for main branch only diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 2624514a3..e184192f1 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -93,6 +93,20 @@ const EMPTY_WORKTREES: ReturnType['getWo const logger = createLogger('Board'); +/** + * The main board view component. + * + * Displays the project board in either Kanban or List layout. + * Manages all board state including: + * - Feature lists and categories + * - Drag and drop operations + * - Worktree management and panel + * - Dialogs (add feature, edit, pipeline settings, etc.) + * - Keyboard shortcuts + * - Auto-mode integration + * + * @returns The rendered Board view + */ export function BoardView() { const { currentProject, @@ -349,7 +363,7 @@ export function BoardView() { const result = await api.worktree.listBranches(currentProject.path); if (result.success && result.result?.branches) { const localBranches = result.result.branches - .filter((b) => !b.isRemote) + .filter((b) => !b.isRemote && b.name) .map((b) => b.name); setBranchSuggestions(localBranches); } diff --git a/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx index f072f7330..f2686b1f2 100644 --- a/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx @@ -37,6 +37,20 @@ interface CreatePRDialogProps { defaultBaseBranch?: string; } +/** + * Dialog for creating a GitHub Pull Request from a worktree. + * + * Features: + * - Automatically commits changes if needed + * - Pushes the branch to remote + * - Creates PR via GitHub CLI + * - Supports draft PRs + * - Allows selecting base branch + * - Fallback to browser if gh CLI is missing/failing + * + * @param props - Component props + * @returns The rendered Create PR dialog + */ export function CreatePRDialog({ open, onOpenChange, @@ -67,7 +81,10 @@ export function CreatePRDialog({ // Filter out current worktree branch from the list const branches = useMemo(() => { if (!branchesData?.branches) return []; - return branchesData.branches.map((b) => b.name).filter((name) => name !== worktree?.branch); + return branchesData.branches + .filter((b) => b.name) + .map((b) => b.name) + .filter((name) => name !== worktree?.branch); }, [branchesData?.branches, worktree?.branch]); // Common state reset function to avoid duplication diff --git a/apps/ui/src/components/views/board-view/dialogs/merge-worktree-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/merge-worktree-dialog.tsx index 7bb1440a1..8eb422814 100644 --- a/apps/ui/src/components/views/board-view/dialogs/merge-worktree-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/merge-worktree-dialog.tsx @@ -29,6 +29,18 @@ interface MergeWorktreeDialogProps { onCreateConflictResolutionFeature?: (conflictInfo: MergeConflictInfo) => void; } +/** + * Dialog for merging a worktree branch into another branch (typically main). + * + * Features: + * - Select target branch (defaults to main) + * - Option to delete worktree and branch after merge + * - Conflict detection and resolution workflow + * - Warnings for uncommitted changes + * + * @param props - Component props + * @returns The rendered Merge Worktree dialog + */ export function MergeWorktreeDialog({ open, onOpenChange, @@ -56,7 +68,7 @@ export function MergeWorktreeDialog({ if (result.success && result.result?.branches) { // Filter out the source branch (can't merge into itself) and remote branches const branches = result.result.branches - .filter((b: BranchInfo) => !b.isRemote && b.name !== worktree.branch) + .filter((b: BranchInfo) => !b.isRemote && b.name && b.name !== worktree.branch) .map((b: BranchInfo) => b.name); setAvailableBranches(branches); } diff --git a/apps/ui/src/components/views/graph-view-page.tsx b/apps/ui/src/components/views/graph-view-page.tsx index 96dffb9a8..478505e4d 100644 --- a/apps/ui/src/components/views/graph-view-page.tsx +++ b/apps/ui/src/components/views/graph-view-page.tsx @@ -29,6 +29,19 @@ const logger = createLogger('GraphViewPage'); // Stable empty array to avoid infinite loop in selector const EMPTY_WORKTREES: ReturnType['getWorktrees']> = []; +/** + * The Graph View page component. + * + * Displays the project's features in a dependency graph visualization. + * Integrates with: + * - D3-based graph rendering (GraphView) + * - Worktree management + * - Feature management (Add, Edit, Delete) + * - Backlog planning + * - Auto-mode task execution + * + * @returns The rendered Graph View page + */ export function GraphViewPage() { const { currentProject, @@ -134,7 +147,7 @@ export function GraphViewPage() { const result = await api.worktree.listBranches(currentProject.path); if (result.success && result.result?.branches) { const localBranches = result.result.branches - .filter((b) => !b.isRemote) + .filter((b) => !b.isRemote && b.name) .map((b) => b.name); setBranchSuggestions(localBranches); }