mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
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.
This commit is contained in:
@@ -8,6 +8,7 @@ interface BranchAutocompleteProps {
|
|||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
branches: string[];
|
branches: string[];
|
||||||
|
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -19,6 +20,7 @@ export function BranchAutocomplete({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
branches,
|
branches,
|
||||||
|
branchCardCounts,
|
||||||
placeholder = "Select a branch...",
|
placeholder = "Select a branch...",
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@@ -28,12 +30,22 @@ export function BranchAutocomplete({
|
|||||||
// Always include "main" at the top of suggestions
|
// Always include "main" at the top of suggestions
|
||||||
const branchOptions: AutocompleteOption[] = React.useMemo(() => {
|
const branchOptions: AutocompleteOption[] = React.useMemo(() => {
|
||||||
const branchSet = new Set(["main", ...branches]);
|
const branchSet = new Set(["main", ...branches]);
|
||||||
return Array.from(branchSet).map((branch) => ({
|
return Array.from(branchSet).map((branch) => {
|
||||||
value: branch,
|
const cardCount = branchCardCounts?.[branch];
|
||||||
label: branch,
|
// Show card count if available, otherwise show "default" for main branch only
|
||||||
badge: branch === "main" ? "default" : undefined,
|
const badge = cardCount !== undefined
|
||||||
}));
|
? String(cardCount)
|
||||||
}, [branches]);
|
: branch === "main"
|
||||||
|
? "default"
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: branch,
|
||||||
|
label: branch,
|
||||||
|
badge,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [branches, branchCardCounts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
|||||||
@@ -270,6 +270,21 @@ export function BoardView() {
|
|||||||
fetchBranches();
|
fetchBranches();
|
||||||
}, [currentProject, worktreeRefreshKey]);
|
}, [currentProject, worktreeRefreshKey]);
|
||||||
|
|
||||||
|
// Calculate unarchived card counts per branch
|
||||||
|
const branchCardCounts = useMemo(() => {
|
||||||
|
const counts: Record<string, number> = {};
|
||||||
|
|
||||||
|
// 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
|
// Custom collision detection that prioritizes columns over cards
|
||||||
const collisionDetectionStrategy = useCallback((args: any) => {
|
const collisionDetectionStrategy = useCallback((args: any) => {
|
||||||
// First, check if pointer is within a column
|
// First, check if pointer is within a column
|
||||||
@@ -833,6 +848,7 @@ export function BoardView() {
|
|||||||
}}
|
}}
|
||||||
onRemovedWorktrees={handleRemovedWorktrees}
|
onRemovedWorktrees={handleRemovedWorktrees}
|
||||||
runningFeatureIds={runningAutoTasks}
|
runningFeatureIds={runningAutoTasks}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
features={hookFeatures.map((f) => ({
|
features={hookFeatures.map((f) => ({
|
||||||
id: f.id,
|
id: f.id,
|
||||||
branchName: f.branchName,
|
branchName: f.branchName,
|
||||||
@@ -929,6 +945,7 @@ export function BoardView() {
|
|||||||
onAdd={handleAddFeature}
|
onAdd={handleAddFeature}
|
||||||
categorySuggestions={categorySuggestions}
|
categorySuggestions={categorySuggestions}
|
||||||
branchSuggestions={branchSuggestions}
|
branchSuggestions={branchSuggestions}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
defaultSkipTests={defaultSkipTests}
|
defaultSkipTests={defaultSkipTests}
|
||||||
defaultBranch={selectedWorktreeBranch}
|
defaultBranch={selectedWorktreeBranch}
|
||||||
currentBranch={currentWorktreeBranch || undefined}
|
currentBranch={currentWorktreeBranch || undefined}
|
||||||
@@ -944,6 +961,7 @@ export function BoardView() {
|
|||||||
onUpdate={handleUpdateFeature}
|
onUpdate={handleUpdateFeature}
|
||||||
categorySuggestions={categorySuggestions}
|
categorySuggestions={categorySuggestions}
|
||||||
branchSuggestions={branchSuggestions}
|
branchSuggestions={branchSuggestions}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
currentBranch={currentWorktreeBranch || undefined}
|
currentBranch={currentWorktreeBranch || undefined}
|
||||||
isMaximized={isMaximized}
|
isMaximized={isMaximized}
|
||||||
showProfilesOnly={showProfilesOnly}
|
showProfilesOnly={showProfilesOnly}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ interface AddFeatureDialogProps {
|
|||||||
}) => void;
|
}) => void;
|
||||||
categorySuggestions: string[];
|
categorySuggestions: string[];
|
||||||
branchSuggestions: string[];
|
branchSuggestions: string[];
|
||||||
|
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
|
||||||
defaultSkipTests: boolean;
|
defaultSkipTests: boolean;
|
||||||
defaultBranch?: string;
|
defaultBranch?: string;
|
||||||
currentBranch?: string;
|
currentBranch?: string;
|
||||||
@@ -87,6 +88,7 @@ export function AddFeatureDialog({
|
|||||||
onAdd,
|
onAdd,
|
||||||
categorySuggestions,
|
categorySuggestions,
|
||||||
branchSuggestions,
|
branchSuggestions,
|
||||||
|
branchCardCounts,
|
||||||
defaultSkipTests,
|
defaultSkipTests,
|
||||||
defaultBranch = "main",
|
defaultBranch = "main",
|
||||||
currentBranch,
|
currentBranch,
|
||||||
@@ -115,11 +117,16 @@ export function AddFeatureDialog({
|
|||||||
const [enhancementMode, setEnhancementMode] = useState<
|
const [enhancementMode, setEnhancementMode] = useState<
|
||||||
"improve" | "technical" | "simplify" | "acceptance"
|
"improve" | "technical" | "simplify" | "acceptance"
|
||||||
>("improve");
|
>("improve");
|
||||||
const [planningMode, setPlanningMode] = useState<PlanningMode>('skip');
|
const [planningMode, setPlanningMode] = useState<PlanningMode>("skip");
|
||||||
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
|
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
|
||||||
|
|
||||||
// Get enhancement model, planning mode defaults, and worktrees setting from store
|
// 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
|
// Sync defaults when dialog opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,7 +140,13 @@ export function AddFeatureDialog({
|
|||||||
setPlanningMode(defaultPlanningMode);
|
setPlanningMode(defaultPlanningMode);
|
||||||
setRequirePlanApproval(defaultRequirePlanApproval);
|
setRequirePlanApproval(defaultRequirePlanApproval);
|
||||||
}
|
}
|
||||||
}, [open, defaultSkipTests, defaultBranch, defaultPlanningMode, defaultRequirePlanApproval]);
|
}, [
|
||||||
|
open,
|
||||||
|
defaultSkipTests,
|
||||||
|
defaultBranch,
|
||||||
|
defaultPlanningMode,
|
||||||
|
defaultRequirePlanApproval,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
if (!newFeature.description.trim()) {
|
if (!newFeature.description.trim()) {
|
||||||
@@ -157,7 +170,7 @@ export function AddFeatureDialog({
|
|||||||
// If currentBranch is provided (non-primary worktree), use it
|
// If currentBranch is provided (non-primary worktree), use it
|
||||||
// Otherwise (primary worktree), use empty string which means "unassigned" (show only on primary)
|
// Otherwise (primary worktree), use empty string which means "unassigned" (show only on primary)
|
||||||
const finalBranchName = useCurrentBranch
|
const finalBranchName = useCurrentBranch
|
||||||
? (currentBranch || "")
|
? currentBranch || ""
|
||||||
: newFeature.branchName || "";
|
: newFeature.branchName || "";
|
||||||
|
|
||||||
onAdd({
|
onAdd({
|
||||||
@@ -398,6 +411,7 @@ export function AddFeatureDialog({
|
|||||||
setNewFeature({ ...newFeature, branchName: value })
|
setNewFeature({ ...newFeature, branchName: value })
|
||||||
}
|
}
|
||||||
branchSuggestions={branchSuggestions}
|
branchSuggestions={branchSuggestions}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
currentBranch={currentBranch}
|
currentBranch={currentBranch}
|
||||||
testIdPrefix="feature"
|
testIdPrefix="feature"
|
||||||
/>
|
/>
|
||||||
@@ -480,7 +494,10 @@ export function AddFeatureDialog({
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Options Tab */}
|
{/* Options Tab */}
|
||||||
<TabsContent value="options" className="space-y-4 overflow-y-auto cursor-default">
|
<TabsContent
|
||||||
|
value="options"
|
||||||
|
className="space-y-4 overflow-y-auto cursor-default"
|
||||||
|
>
|
||||||
{/* Planning Mode Section */}
|
{/* Planning Mode Section */}
|
||||||
<PlanningModeSelector
|
<PlanningModeSelector
|
||||||
mode={planningMode}
|
mode={planningMode}
|
||||||
@@ -515,9 +532,7 @@ export function AddFeatureDialog({
|
|||||||
hotkeyActive={open}
|
hotkeyActive={open}
|
||||||
data-testid="confirm-add-feature"
|
data-testid="confirm-add-feature"
|
||||||
disabled={
|
disabled={
|
||||||
useWorktrees &&
|
useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()
|
||||||
!useCurrentBranch &&
|
|
||||||
!newFeature.branchName.trim()
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Add Feature
|
Add Feature
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ interface EditFeatureDialogProps {
|
|||||||
) => void;
|
) => void;
|
||||||
categorySuggestions: string[];
|
categorySuggestions: string[];
|
||||||
branchSuggestions: string[];
|
branchSuggestions: string[];
|
||||||
|
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
|
||||||
currentBranch?: string;
|
currentBranch?: string;
|
||||||
isMaximized: boolean;
|
isMaximized: boolean;
|
||||||
showProfilesOnly: boolean;
|
showProfilesOnly: boolean;
|
||||||
@@ -90,6 +91,7 @@ export function EditFeatureDialog({
|
|||||||
onUpdate,
|
onUpdate,
|
||||||
categorySuggestions,
|
categorySuggestions,
|
||||||
branchSuggestions,
|
branchSuggestions,
|
||||||
|
branchCardCounts,
|
||||||
currentBranch,
|
currentBranch,
|
||||||
isMaximized,
|
isMaximized,
|
||||||
showProfilesOnly,
|
showProfilesOnly,
|
||||||
@@ -389,6 +391,7 @@ export function EditFeatureDialog({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
branchSuggestions={branchSuggestions}
|
branchSuggestions={branchSuggestions}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
currentBranch={currentBranch}
|
currentBranch={currentBranch}
|
||||||
disabled={editingFeature.status !== "backlog"}
|
disabled={editingFeature.status !== "backlog"}
|
||||||
testIdPrefix="edit-feature"
|
testIdPrefix="edit-feature"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface BranchSelectorProps {
|
|||||||
branchName: string;
|
branchName: string;
|
||||||
onBranchNameChange: (branchName: string) => void;
|
onBranchNameChange: (branchName: string) => void;
|
||||||
branchSuggestions: string[];
|
branchSuggestions: string[];
|
||||||
|
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
|
||||||
currentBranch?: string;
|
currentBranch?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
testIdPrefix?: string;
|
testIdPrefix?: string;
|
||||||
@@ -21,6 +22,7 @@ export function BranchSelector({
|
|||||||
branchName,
|
branchName,
|
||||||
onBranchNameChange,
|
onBranchNameChange,
|
||||||
branchSuggestions,
|
branchSuggestions,
|
||||||
|
branchCardCounts,
|
||||||
currentBranch,
|
currentBranch,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
testIdPrefix = "branch",
|
testIdPrefix = "branch",
|
||||||
@@ -69,6 +71,7 @@ export function BranchSelector({
|
|||||||
value={branchName}
|
value={branchName}
|
||||||
onChange={onBranchNameChange}
|
onChange={onBranchNameChange}
|
||||||
branches={branchSuggestions}
|
branches={branchSuggestions}
|
||||||
|
branchCardCounts={branchCardCounts}
|
||||||
placeholder="Select or create branch..."
|
placeholder="Select or create branch..."
|
||||||
data-testid={`${testIdPrefix}-input`}
|
data-testid={`${testIdPrefix}-input`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { WorktreeActionsDropdown } from "./worktree-actions-dropdown";
|
|||||||
|
|
||||||
interface WorktreeTabProps {
|
interface WorktreeTabProps {
|
||||||
worktree: WorktreeInfo;
|
worktree: WorktreeInfo;
|
||||||
|
cardCount?: number; // Number of unarchived cards for this branch
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
isActivating: boolean;
|
isActivating: boolean;
|
||||||
@@ -44,6 +45,7 @@ interface WorktreeTabProps {
|
|||||||
|
|
||||||
export function WorktreeTab({
|
export function WorktreeTab({
|
||||||
worktree,
|
worktree,
|
||||||
|
cardCount,
|
||||||
isSelected,
|
isSelected,
|
||||||
isRunning,
|
isRunning,
|
||||||
isActivating,
|
isActivating,
|
||||||
@@ -97,9 +99,9 @@ export function WorktreeTab({
|
|||||||
<RefreshCw className="w-3 h-3 animate-spin" />
|
<RefreshCw className="w-3 h-3 animate-spin" />
|
||||||
)}
|
)}
|
||||||
{worktree.branch}
|
{worktree.branch}
|
||||||
{worktree.hasChanges && (
|
{cardCount !== undefined && cardCount > 0 && (
|
||||||
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">
|
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">
|
||||||
{worktree.changedFilesCount}
|
{cardCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -140,9 +142,9 @@ export function WorktreeTab({
|
|||||||
<RefreshCw className="w-3 h-3 animate-spin" />
|
<RefreshCw className="w-3 h-3 animate-spin" />
|
||||||
)}
|
)}
|
||||||
{worktree.branch}
|
{worktree.branch}
|
||||||
{worktree.hasChanges && (
|
{cardCount !== undefined && cardCount > 0 && (
|
||||||
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">
|
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">
|
||||||
{worktree.changedFilesCount}
|
{cardCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ export interface WorktreePanelProps {
|
|||||||
onRemovedWorktrees?: (removedWorktrees: Array<{ path: string; branch: string }>) => void;
|
onRemovedWorktrees?: (removedWorktrees: Array<{ path: string; branch: string }>) => void;
|
||||||
runningFeatureIds?: string[];
|
runningFeatureIds?: string[];
|
||||||
features?: FeatureInfo[];
|
features?: FeatureInfo[];
|
||||||
|
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
|
||||||
refreshTrigger?: number;
|
refreshTrigger?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function WorktreePanel({
|
|||||||
onRemovedWorktrees,
|
onRemovedWorktrees,
|
||||||
runningFeatureIds = [],
|
runningFeatureIds = [],
|
||||||
features = [],
|
features = [],
|
||||||
|
branchCardCounts,
|
||||||
refreshTrigger = 0,
|
refreshTrigger = 0,
|
||||||
}: WorktreePanelProps) {
|
}: WorktreePanelProps) {
|
||||||
const {
|
const {
|
||||||
@@ -110,43 +111,47 @@ export function WorktreePanel({
|
|||||||
<span className="text-sm text-muted-foreground mr-2">Branch:</span>
|
<span className="text-sm text-muted-foreground mr-2">Branch:</span>
|
||||||
|
|
||||||
<div className="flex items-center gap-1 flex-wrap">
|
<div className="flex items-center gap-1 flex-wrap">
|
||||||
{worktrees.map((worktree) => (
|
{worktrees.map((worktree) => {
|
||||||
<WorktreeTab
|
const cardCount = branchCardCounts?.[worktree.branch];
|
||||||
key={worktree.path}
|
return (
|
||||||
worktree={worktree}
|
<WorktreeTab
|
||||||
isSelected={isWorktreeSelected(worktree)}
|
key={worktree.path}
|
||||||
isRunning={hasRunningFeatures(worktree)}
|
worktree={worktree}
|
||||||
isActivating={isActivating}
|
cardCount={cardCount}
|
||||||
isDevServerRunning={isDevServerRunning(worktree)}
|
isSelected={isWorktreeSelected(worktree)}
|
||||||
devServerInfo={getDevServerInfo(worktree)}
|
isRunning={hasRunningFeatures(worktree)}
|
||||||
defaultEditorName={defaultEditorName}
|
isActivating={isActivating}
|
||||||
branches={branches}
|
isDevServerRunning={isDevServerRunning(worktree)}
|
||||||
filteredBranches={filteredBranches}
|
devServerInfo={getDevServerInfo(worktree)}
|
||||||
branchFilter={branchFilter}
|
defaultEditorName={defaultEditorName}
|
||||||
isLoadingBranches={isLoadingBranches}
|
branches={branches}
|
||||||
isSwitching={isSwitching}
|
filteredBranches={filteredBranches}
|
||||||
isPulling={isPulling}
|
branchFilter={branchFilter}
|
||||||
isPushing={isPushing}
|
isLoadingBranches={isLoadingBranches}
|
||||||
isStartingDevServer={isStartingDevServer}
|
isSwitching={isSwitching}
|
||||||
aheadCount={aheadCount}
|
isPulling={isPulling}
|
||||||
behindCount={behindCount}
|
isPushing={isPushing}
|
||||||
onSelectWorktree={handleSelectWorktree}
|
isStartingDevServer={isStartingDevServer}
|
||||||
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)}
|
aheadCount={aheadCount}
|
||||||
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)}
|
behindCount={behindCount}
|
||||||
onBranchFilterChange={setBranchFilter}
|
onSelectWorktree={handleSelectWorktree}
|
||||||
onSwitchBranch={handleSwitchBranch}
|
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)}
|
||||||
onCreateBranch={onCreateBranch}
|
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)}
|
||||||
onPull={handlePull}
|
onBranchFilterChange={setBranchFilter}
|
||||||
onPush={handlePush}
|
onSwitchBranch={handleSwitchBranch}
|
||||||
onOpenInEditor={handleOpenInEditor}
|
onCreateBranch={onCreateBranch}
|
||||||
onCommit={onCommit}
|
onPull={handlePull}
|
||||||
onCreatePR={onCreatePR}
|
onPush={handlePush}
|
||||||
onDeleteWorktree={onDeleteWorktree}
|
onOpenInEditor={handleOpenInEditor}
|
||||||
onStartDevServer={handleStartDevServer}
|
onCommit={onCommit}
|
||||||
onStopDevServer={handleStopDevServer}
|
onCreatePR={onCreatePR}
|
||||||
onOpenDevServerUrl={handleOpenDevServerUrl}
|
onDeleteWorktree={onDeleteWorktree}
|
||||||
/>
|
onStartDevServer={handleStartDevServer}
|
||||||
))}
|
onStopDevServer={handleStopDevServer}
|
||||||
|
onOpenDevServerUrl={handleOpenDevServerUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
Reference in New Issue
Block a user