refactor: Remove commit actions and update badge logic in Kanban components

- Removed the onCommit action from KanbanBoard and related components to streamline functionality.
- Updated CardActions to replace the Commit button with a Mark as Verified button, enhancing clarity in user interactions.
- Introduced a new CardBadge component for consistent styling of badges across KanbanCard, improving code reusability and maintainability.
- Refactored badge rendering logic to include a Just Finished badge, ensuring accurate representation of feature status.
This commit is contained in:
Test User
2025-12-20 12:45:51 -05:00
parent 01d78be748
commit 723274523d
4 changed files with 132 additions and 112 deletions

View File

@@ -6,7 +6,6 @@ import {
RotateCcw, RotateCcw,
StopCircle, StopCircle,
CheckCircle2, CheckCircle2,
GitCommit,
FileText, FileText,
Eye, Eye,
Wand2, Wand2,
@@ -25,7 +24,6 @@ interface CardActionsProps {
onForceStop?: () => void; onForceStop?: () => void;
onManualVerify?: () => void; onManualVerify?: () => void;
onFollowUp?: () => void; onFollowUp?: () => void;
onCommit?: () => void;
onImplement?: () => void; onImplement?: () => void;
onComplete?: () => void; onComplete?: () => void;
onViewPlan?: () => void; onViewPlan?: () => void;
@@ -44,7 +42,6 @@ export function CardActions({
onForceStop, onForceStop,
onManualVerify, onManualVerify,
onFollowUp, onFollowUp,
onCommit,
onImplement, onImplement,
onComplete, onComplete,
onViewPlan, onViewPlan,
@@ -251,7 +248,7 @@ export function CardActions({
<span className="truncate">Refine</span> <span className="truncate">Refine</span>
</Button> </Button>
)} )}
{/* Show Verify button if PR was created (changes are committed), otherwise show Commit button */} {/* Show Verify button if PR was created (changes are committed), otherwise show Mark as Verified button */}
{feature.prUrl && onManualVerify ? ( {feature.prUrl && onManualVerify ? (
<Button <Button
variant="default" variant="default"
@@ -267,20 +264,20 @@ export function CardActions({
<CheckCircle2 className="w-3 h-3 mr-1" /> <CheckCircle2 className="w-3 h-3 mr-1" />
Verify Verify
</Button> </Button>
) : onCommit ? ( ) : onManualVerify ? (
<Button <Button
variant="default" variant="default"
size="sm" size="sm"
className="flex-1 h-7 text-[11px]" className="flex-1 h-7 text-[11px]"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onCommit(); onManualVerify();
}} }}
onPointerDown={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}
data-testid={`commit-${feature.id}`} data-testid={`mark-as-verified-${feature.id}`}
> >
<GitCommit className="w-3 h-3 mr-1" /> <CheckCircle2 className="w-3 h-3 mr-1" />
Commit Mark as Verified
</Button> </Button>
) : null} ) : null}
</> </>
@@ -338,4 +335,3 @@ export function CardActions({
</div> </div>
); );
} }

View File

@@ -7,16 +7,46 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { AlertCircle, Lock, Sparkles, Hand } from "lucide-react"; import { AlertCircle, Lock, Hand, Sparkles } from "lucide-react";
import { getBlockingDependencies } from "@/lib/dependency-resolver"; import { getBlockingDependencies } from "@/lib/dependency-resolver";
interface CardBadgeProps {
children: React.ReactNode;
className?: string;
"data-testid"?: string;
title?: string;
}
/**
* Shared badge component matching the "Just Finished" badge style
* Used for priority badges and other card badges
*/
function CardBadge({
children,
className,
"data-testid": dataTestId,
title,
}: CardBadgeProps) {
return (
<div
className={cn(
"inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium",
className
)}
data-testid={dataTestId}
title={title}
>
{children}
</div>
);
}
interface CardBadgesProps { interface CardBadgesProps {
feature: Feature; feature: Feature;
} }
export function CardBadges({ feature }: CardBadgesProps) { export function CardBadges({ feature }: CardBadgesProps) {
const { enableDependencyBlocking, features } = useAppStore(); const { enableDependencyBlocking, features } = useAppStore();
const [currentTime, setCurrentTime] = useState(() => Date.now());
// Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies) // Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies)
const blockingDependencies = useMemo(() => { const blockingDependencies = useMemo(() => {
@@ -26,50 +56,13 @@ export function CardBadges({ feature }: CardBadgesProps) {
return getBlockingDependencies(feature, features); return getBlockingDependencies(feature, features);
}, [enableDependencyBlocking, feature, features]); }, [enableDependencyBlocking, feature, features]);
const isJustFinished = useMemo(() => { // Status badges row (error, blocked)
if (
!feature.justFinishedAt ||
feature.status !== "waiting_approval" ||
feature.error
) {
return false;
}
const finishedTime = new Date(feature.justFinishedAt).getTime();
const twoMinutes = 2 * 60 * 1000;
return currentTime - finishedTime < twoMinutes;
}, [feature.justFinishedAt, feature.status, feature.error, currentTime]);
useEffect(() => {
if (!feature.justFinishedAt || feature.status !== "waiting_approval") {
return;
}
const finishedTime = new Date(feature.justFinishedAt).getTime();
const twoMinutes = 2 * 60 * 1000;
const timeRemaining = twoMinutes - (currentTime - finishedTime);
if (timeRemaining <= 0) {
return;
}
// eslint-disable-next-line no-undef
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 1000);
return () => {
// eslint-disable-next-line no-undef
clearInterval(interval);
};
}, [feature.justFinishedAt, feature.status, currentTime]);
// Status badges row (error, blocked, just finished)
const showStatusBadges = const showStatusBadges =
feature.error || feature.error ||
(blockingDependencies.length > 0 && (blockingDependencies.length > 0 &&
!feature.error && !feature.error &&
!feature.skipTests && !feature.skipTests &&
feature.status === "backlog") || feature.status === "backlog");
isJustFinished;
if (!showStatusBadges) { if (!showStatusBadges) {
return null; return null;
@@ -117,13 +110,12 @@ export function CardBadges({ feature }: CardBadgesProps) {
<Lock className="w-3 h-3" /> <Lock className="w-3 h-3" />
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent <TooltipContent side="bottom" className="text-xs max-w-[250px]">
side="bottom"
className="text-xs max-w-[250px]"
>
<p className="font-medium mb-1"> <p className="font-medium mb-1">
Blocked by {blockingDependencies.length} incomplete{" "} Blocked by {blockingDependencies.length} incomplete{" "}
{blockingDependencies.length === 1 ? "dependency" : "dependencies"} {blockingDependencies.length === 1
? "dependency"
: "dependencies"}
</p> </p>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
{blockingDependencies {blockingDependencies
@@ -137,21 +129,6 @@ export function CardBadges({ feature }: CardBadgesProps) {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
{/* Just Finished badge */}
{isJustFinished && (
<div
className={cn(
"inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium",
"bg-[var(--status-success-bg)] border-[var(--status-success)]/40 text-[var(--status-success)]",
"animate-pulse"
)}
data-testid={`just-finished-badge-${feature.id}`}
title="Agent just finished working on this feature"
>
<Sparkles className="w-3 h-3" />
</div>
)}
</div> </div>
); );
} }
@@ -161,11 +138,49 @@ interface PriorityBadgesProps {
} }
export function PriorityBadges({ feature }: PriorityBadgesProps) { export function PriorityBadges({ feature }: PriorityBadgesProps) {
const [currentTime, setCurrentTime] = useState(() => Date.now());
const isJustFinished = useMemo(() => {
if (
!feature.justFinishedAt ||
feature.status !== "waiting_approval" ||
feature.error
) {
return false;
}
const finishedTime = new Date(feature.justFinishedAt).getTime();
const twoMinutes = 2 * 60 * 1000;
return currentTime - finishedTime < twoMinutes;
}, [feature.justFinishedAt, feature.status, feature.error, currentTime]);
useEffect(() => {
if (!feature.justFinishedAt || feature.status !== "waiting_approval") {
return;
}
const finishedTime = new Date(feature.justFinishedAt).getTime();
const twoMinutes = 2 * 60 * 1000;
const timeRemaining = twoMinutes - (currentTime - finishedTime);
if (timeRemaining <= 0) {
return;
}
// eslint-disable-next-line no-undef
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 1000);
return () => {
// eslint-disable-next-line no-undef
clearInterval(interval);
};
}, [feature.justFinishedAt, feature.status, currentTime]);
const showPriorityBadges = const showPriorityBadges =
feature.priority || feature.priority ||
(feature.skipTests && (feature.skipTests && !feature.error && feature.status === "backlog") ||
!feature.error && isJustFinished;
feature.status === "backlog");
if (!showPriorityBadges) { if (!showPriorityBadges) {
return null; return null;
@@ -178,24 +193,32 @@ export function PriorityBadges({ feature }: PriorityBadgesProps) {
<TooltipProvider delayDuration={200}> <TooltipProvider delayDuration={200}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <CardBadge
className={cn( className={cn(
"inline-flex items-center justify-center gap-1 rounded-full border-2 px-1.5 py-0.5 text-[10px] font-bold", "bg-opacity-90 border rounded-[6px] px-1.5 py-0.5 flex items-center justify-center border-[1.5px] w-5 h-5", // badge style from example
feature.priority === 1 && feature.priority === 1 &&
"bg-red-500/20 text-red-500 border-red-500/50", "bg-[var(--status-error-bg)] border-[var(--status-error)]/40 text-[var(--status-error)]",
feature.priority === 2 && feature.priority === 2 &&
"bg-yellow-500/20 text-yellow-500 border-yellow-500/50", "bg-[var(--status-warning-bg)] border-[var(--status-warning)]/40 text-[var(--status-warning)]",
feature.priority === 3 && feature.priority === 3 &&
"bg-blue-500/20 text-blue-500 border-blue-500/50" "bg-[var(--status-info-bg)] border-[var(--status-info)]/40 text-[var(--status-info)]"
)} )}
data-testid={`priority-badge-${feature.id}`} data-testid={`priority-badge-${feature.id}`}
> >
{feature.priority === 1 {feature.priority === 1 ? (
? "H" <span className="font-bold text-xs flex items-center gap-0.5">
: feature.priority === 2 H
? "M" </span>
: "L"} ) : feature.priority === 2 ? (
</div> <span className="font-bold text-xs flex items-center gap-0.5">
M
</span>
) : (
<span className="font-bold text-xs flex items-center gap-0.5">
L
</span>
)}
</CardBadge>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="bottom" className="text-xs"> <TooltipContent side="bottom" className="text-xs">
<p> <p>
@@ -210,21 +233,16 @@ export function PriorityBadges({ feature }: PriorityBadgesProps) {
</TooltipProvider> </TooltipProvider>
)} )}
{/* Manual verification badge */} {/* Manual verification badge */}
{feature.skipTests && {feature.skipTests && !feature.error && feature.status === "backlog" && (
!feature.error &&
feature.status === "backlog" && (
<TooltipProvider delayDuration={200}> <TooltipProvider delayDuration={200}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <CardBadge
className={cn( className="bg-[var(--status-warning-bg)] border-[var(--status-warning)]/40 text-[var(--status-warning)]"
"inline-flex items-center gap-1 rounded-full border-2 px-1.5 py-0.5 text-[10px] font-bold",
"bg-[var(--status-warning-bg)] border-[var(--status-warning)]/50 text-[var(--status-warning)]"
)}
data-testid={`skip-tests-badge-${feature.id}`} data-testid={`skip-tests-badge-${feature.id}`}
> >
<Hand className="w-3 h-3" /> <Hand className="w-3 h-3" />
</div> </CardBadge>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="bottom" className="text-xs"> <TooltipContent side="bottom" className="text-xs">
<p>Manual verification required</p> <p>Manual verification required</p>
@@ -232,7 +250,17 @@ export function PriorityBadges({ feature }: PriorityBadgesProps) {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
{/* Just Finished badge */}
{isJustFinished && (
<CardBadge
className="bg-[var(--status-success-bg)] border-[var(--status-success)]/40 text-[var(--status-success)] animate-pulse"
data-testid={`just-finished-badge-${feature.id}`}
title="Agent just finished working on this feature"
>
<Sparkles className="w-3 h-3" />
</CardBadge>
)}
</div> </div>
); );
} }

View File

@@ -21,7 +21,6 @@ interface KanbanCardProps {
onManualVerify?: () => void; onManualVerify?: () => void;
onMoveBackToInProgress?: () => void; onMoveBackToInProgress?: () => void;
onFollowUp?: () => void; onFollowUp?: () => void;
onCommit?: () => void;
onImplement?: () => void; onImplement?: () => void;
onComplete?: () => void; onComplete?: () => void;
onViewPlan?: () => void; onViewPlan?: () => void;
@@ -48,7 +47,6 @@ export const KanbanCard = memo(function KanbanCard({
onManualVerify, onManualVerify,
onMoveBackToInProgress: _onMoveBackToInProgress, onMoveBackToInProgress: _onMoveBackToInProgress,
onFollowUp, onFollowUp,
onCommit,
onImplement, onImplement,
onComplete, onComplete,
onViewPlan, onViewPlan,
@@ -198,7 +196,6 @@ export const KanbanCard = memo(function KanbanCard({
onForceStop={onForceStop} onForceStop={onForceStop}
onManualVerify={onManualVerify} onManualVerify={onManualVerify}
onFollowUp={onFollowUp} onFollowUp={onFollowUp}
onCommit={onCommit}
onImplement={onImplement} onImplement={onImplement}
onComplete={onComplete} onComplete={onComplete}
onViewPlan={onViewPlan} onViewPlan={onViewPlan}

View File

@@ -194,7 +194,6 @@ export function KanbanBoard({
onMoveBackToInProgress(feature) onMoveBackToInProgress(feature)
} }
onFollowUp={() => onFollowUp(feature)} onFollowUp={() => onFollowUp(feature)}
onCommit={() => onCommit(feature)}
onComplete={() => onComplete(feature)} onComplete={() => onComplete(feature)}
onImplement={() => onImplement(feature)} onImplement={() => onImplement(feature)}
onViewPlan={() => onViewPlan(feature)} onViewPlan={() => onViewPlan(feature)}