From f0bea761417af2f08d7ace0c8d235467e1263ab3 Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Thu, 18 Dec 2025 17:34:37 -0500 Subject: [PATCH 1/6] feat: add branch card counts to UI components - Introduced branchCardCounts prop to various components to display unarchived card counts per branch. - Updated BranchAutocomplete, BoardView, AddFeatureDialog, EditFeatureDialog, BranchSelector, WorktreePanel, and WorktreeTab to utilize the new prop for enhanced branch management visibility. - Enhanced user experience by showing card counts alongside branch names in relevant UI elements. --- .../src/components/ui/branch-autocomplete.tsx | 24 ++++-- apps/app/src/components/views/board-view.tsx | 18 +++++ .../board-view/dialogs/add-feature-dialog.tsx | 31 ++++++-- .../dialogs/edit-feature-dialog.tsx | 3 + .../board-view/shared/branch-selector.tsx | 3 + .../components/worktree-tab.tsx | 10 ++- .../views/board-view/worktree-panel/types.ts | 1 + .../worktree-panel/worktree-panel.tsx | 79 ++++++++++--------- 8 files changed, 114 insertions(+), 55 deletions(-) diff --git a/apps/app/src/components/ui/branch-autocomplete.tsx b/apps/app/src/components/ui/branch-autocomplete.tsx index b2d76913..1dbd32f0 100644 --- a/apps/app/src/components/ui/branch-autocomplete.tsx +++ b/apps/app/src/components/ui/branch-autocomplete.tsx @@ -8,6 +8,7 @@ interface BranchAutocompleteProps { value: string; onChange: (value: string) => void; branches: string[]; + branchCardCounts?: Record; // Map of branch name to unarchived card count placeholder?: string; className?: string; disabled?: boolean; @@ -19,6 +20,7 @@ export function BranchAutocomplete({ value, onChange, branches, + branchCardCounts, placeholder = "Select a branch...", className, disabled = false, @@ -28,12 +30,22 @@ export function BranchAutocomplete({ // Always include "main" at the top of suggestions const branchOptions: AutocompleteOption[] = React.useMemo(() => { const branchSet = new Set(["main", ...branches]); - return Array.from(branchSet).map((branch) => ({ - value: branch, - label: branch, - badge: branch === "main" ? "default" : undefined, - })); - }, [branches]); + return Array.from(branchSet).map((branch) => { + const cardCount = branchCardCounts?.[branch]; + // Show card count if available, otherwise show "default" for main branch only + const badge = cardCount !== undefined + ? String(cardCount) + : branch === "main" + ? "default" + : undefined; + + return { + value: branch, + label: branch, + badge, + }; + }); + }, [branches, branchCardCounts]); return ( { + const counts: Record = {}; + + // Count unarchived features (status !== "completed") per branch + hookFeatures.forEach((feature) => { + if (feature.status !== "completed") { + const branch = feature.branchName || "main"; + counts[branch] = (counts[branch] || 0) + 1; + } + }); + + return counts; + }, [hookFeatures]); + // Custom collision detection that prioritizes columns over cards const collisionDetectionStrategy = useCallback((args: any) => { // First, check if pointer is within a column @@ -833,6 +848,7 @@ export function BoardView() { }} onRemovedWorktrees={handleRemovedWorktrees} runningFeatureIds={runningAutoTasks} + branchCardCounts={branchCardCounts} features={hookFeatures.map((f) => ({ id: f.id, branchName: f.branchName, @@ -929,6 +945,7 @@ export function BoardView() { onAdd={handleAddFeature} categorySuggestions={categorySuggestions} branchSuggestions={branchSuggestions} + branchCardCounts={branchCardCounts} defaultSkipTests={defaultSkipTests} defaultBranch={selectedWorktreeBranch} currentBranch={currentWorktreeBranch || undefined} @@ -944,6 +961,7 @@ export function BoardView() { onUpdate={handleUpdateFeature} categorySuggestions={categorySuggestions} branchSuggestions={branchSuggestions} + branchCardCounts={branchCardCounts} currentBranch={currentWorktreeBranch || undefined} isMaximized={isMaximized} showProfilesOnly={showProfilesOnly} diff --git a/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 51dfd4bd..388c9582 100644 --- a/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -73,6 +73,7 @@ interface AddFeatureDialogProps { }) => void; categorySuggestions: string[]; branchSuggestions: string[]; + branchCardCounts?: Record; // Map of branch name to unarchived card count defaultSkipTests: boolean; defaultBranch?: string; currentBranch?: string; @@ -87,6 +88,7 @@ export function AddFeatureDialog({ onAdd, categorySuggestions, branchSuggestions, + branchCardCounts, defaultSkipTests, defaultBranch = "main", currentBranch, @@ -115,11 +117,16 @@ export function AddFeatureDialog({ const [enhancementMode, setEnhancementMode] = useState< "improve" | "technical" | "simplify" | "acceptance" >("improve"); - const [planningMode, setPlanningMode] = useState('skip'); + const [planningMode, setPlanningMode] = useState("skip"); const [requirePlanApproval, setRequirePlanApproval] = useState(false); // Get enhancement model, planning mode defaults, and worktrees setting from store - const { enhancementModel, defaultPlanningMode, defaultRequirePlanApproval, useWorktrees } = useAppStore(); + const { + enhancementModel, + defaultPlanningMode, + defaultRequirePlanApproval, + useWorktrees, + } = useAppStore(); // Sync defaults when dialog opens useEffect(() => { @@ -133,7 +140,13 @@ export function AddFeatureDialog({ setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); } - }, [open, defaultSkipTests, defaultBranch, defaultPlanningMode, defaultRequirePlanApproval]); + }, [ + open, + defaultSkipTests, + defaultBranch, + defaultPlanningMode, + defaultRequirePlanApproval, + ]); const handleAdd = () => { if (!newFeature.description.trim()) { @@ -157,7 +170,7 @@ export function AddFeatureDialog({ // If currentBranch is provided (non-primary worktree), use it // Otherwise (primary worktree), use empty string which means "unassigned" (show only on primary) const finalBranchName = useCurrentBranch - ? (currentBranch || "") + ? currentBranch || "" : newFeature.branchName || ""; onAdd({ @@ -398,6 +411,7 @@ export function AddFeatureDialog({ setNewFeature({ ...newFeature, branchName: value }) } branchSuggestions={branchSuggestions} + branchCardCounts={branchCardCounts} currentBranch={currentBranch} testIdPrefix="feature" /> @@ -480,7 +494,10 @@ export function AddFeatureDialog({ {/* Options Tab */} - + {/* Planning Mode Section */} Add Feature diff --git a/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 56d2757b..6e63bb35 100644 --- a/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -77,6 +77,7 @@ interface EditFeatureDialogProps { ) => void; categorySuggestions: string[]; branchSuggestions: string[]; + branchCardCounts?: Record; // Map of branch name to unarchived card count currentBranch?: string; isMaximized: boolean; showProfilesOnly: boolean; @@ -90,6 +91,7 @@ export function EditFeatureDialog({ onUpdate, categorySuggestions, branchSuggestions, + branchCardCounts, currentBranch, isMaximized, showProfilesOnly, @@ -389,6 +391,7 @@ export function EditFeatureDialog({ }) } branchSuggestions={branchSuggestions} + branchCardCounts={branchCardCounts} currentBranch={currentBranch} disabled={editingFeature.status !== "backlog"} testIdPrefix="edit-feature" diff --git a/apps/app/src/components/views/board-view/shared/branch-selector.tsx b/apps/app/src/components/views/board-view/shared/branch-selector.tsx index a395edf5..0ba0848b 100644 --- a/apps/app/src/components/views/board-view/shared/branch-selector.tsx +++ b/apps/app/src/components/views/board-view/shared/branch-selector.tsx @@ -10,6 +10,7 @@ interface BranchSelectorProps { branchName: string; onBranchNameChange: (branchName: string) => void; branchSuggestions: string[]; + branchCardCounts?: Record; // Map of branch name to unarchived card count currentBranch?: string; disabled?: boolean; testIdPrefix?: string; @@ -21,6 +22,7 @@ export function BranchSelector({ branchName, onBranchNameChange, branchSuggestions, + branchCardCounts, currentBranch, disabled = false, testIdPrefix = "branch", @@ -69,6 +71,7 @@ export function BranchSelector({ value={branchName} onChange={onBranchNameChange} branches={branchSuggestions} + branchCardCounts={branchCardCounts} placeholder="Select or create branch..." data-testid={`${testIdPrefix}-input`} disabled={disabled} diff --git a/apps/app/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx b/apps/app/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx index 8332e71d..4f130db7 100644 --- a/apps/app/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx +++ b/apps/app/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx @@ -9,6 +9,7 @@ import { WorktreeActionsDropdown } from "./worktree-actions-dropdown"; interface WorktreeTabProps { worktree: WorktreeInfo; + cardCount?: number; // Number of unarchived cards for this branch isSelected: boolean; isRunning: boolean; isActivating: boolean; @@ -44,6 +45,7 @@ interface WorktreeTabProps { export function WorktreeTab({ worktree, + cardCount, isSelected, isRunning, isActivating, @@ -97,9 +99,9 @@ export function WorktreeTab({ )} {worktree.branch} - {worktree.hasChanges && ( + {cardCount !== undefined && cardCount > 0 && ( - {worktree.changedFilesCount} + {cardCount} )} @@ -140,9 +142,9 @@ export function WorktreeTab({ )} {worktree.branch} - {worktree.hasChanges && ( + {cardCount !== undefined && cardCount > 0 && ( - {worktree.changedFilesCount} + {cardCount} )} diff --git a/apps/app/src/components/views/board-view/worktree-panel/types.ts b/apps/app/src/components/views/board-view/worktree-panel/types.ts index e143ae73..c1beaf5f 100644 --- a/apps/app/src/components/views/board-view/worktree-panel/types.ts +++ b/apps/app/src/components/views/board-view/worktree-panel/types.ts @@ -35,5 +35,6 @@ export interface WorktreePanelProps { onRemovedWorktrees?: (removedWorktrees: Array<{ path: string; branch: string }>) => void; runningFeatureIds?: string[]; features?: FeatureInfo[]; + branchCardCounts?: Record; // Map of branch name to unarchived card count refreshTrigger?: number; } diff --git a/apps/app/src/components/views/board-view/worktree-panel/worktree-panel.tsx b/apps/app/src/components/views/board-view/worktree-panel/worktree-panel.tsx index ddd27892..910b8793 100644 --- a/apps/app/src/components/views/board-view/worktree-panel/worktree-panel.tsx +++ b/apps/app/src/components/views/board-view/worktree-panel/worktree-panel.tsx @@ -24,6 +24,7 @@ export function WorktreePanel({ onRemovedWorktrees, runningFeatureIds = [], features = [], + branchCardCounts, refreshTrigger = 0, }: WorktreePanelProps) { const { @@ -110,43 +111,47 @@ export function WorktreePanel({ Branch:
- {worktrees.map((worktree) => ( - - ))} + {worktrees.map((worktree) => { + const cardCount = branchCardCounts?.[worktree.branch]; + return ( + + ); + })}