Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion apps/ui/src/components/views/board-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ export function BoardView() {
handleForceStopFeature,
handleStartNextFeatures,
handleArchiveAllVerified,
handleHideFeature,
} = useBoardActions({
currentProject,
features: hookFeatures,
Expand Down Expand Up @@ -744,7 +745,15 @@ export function BoardView() {
});

// Use column features hook
const { getColumnFeatures, completedFeatures } = useBoardColumnFeatures({
const {
getColumnFeatures,
completedFeatures,
backlogPagination,
showMoreBacklog,
showAllBacklog,
toggleShowOnlyHidden,
resetBacklogPagination,
} = useBoardColumnFeatures({
features: hookFeatures,
runningAutoTasks,
searchQuery,
Expand Down Expand Up @@ -1021,13 +1030,19 @@ export function BoardView() {
onImplement={handleStartImplementation}
onViewPlan={(feature) => setViewPlanFeature(feature)}
onApprovePlan={handleOpenApprovalDialog}
onHide={handleHideFeature}
featuresWithContext={featuresWithContext}
runningAutoTasks={runningAutoTasks}
shortcuts={shortcuts}
onStartNextFeatures={handleStartNextFeatures}
onShowSuggestions={() => setShowSuggestionsDialog(true)}
suggestionsCount={suggestionsCount}
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
backlogPagination={backlogPagination}
onShowMoreBacklog={showMoreBacklog}
onShowAllBacklog={showAllBacklog}
onToggleShowOnlyHidden={toggleShowOnlyHidden}
onResetBacklogPagination={resetBacklogPagination}
/>
) : (
<GraphView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Eye,
Wand2,
Archive,
EyeOff,
} from 'lucide-react';

interface CardActionsProps {
Expand All @@ -28,6 +29,7 @@ interface CardActionsProps {
onComplete?: () => void;
onViewPlan?: () => void;
onApprovePlan?: () => void;
onHide?: () => void;
}

export function CardActions({
Expand All @@ -46,6 +48,7 @@ export function CardActions({
onComplete,
onViewPlan,
onApprovePlan,
onHide,
}: CardActionsProps) {
return (
<div className="flex flex-wrap gap-1.5 -mx-3 -mb-3 px-3 pb-3">
Expand Down Expand Up @@ -298,6 +301,22 @@ export function CardActions({
<Edit className="w-3 h-3 mr-1" />
Edit
</Button>
{onHide && (
<Button
variant="outline"
size="sm"
className="h-7 text-xs px-2"
onClick={(e) => {
e.stopPropagation();
onHide();
}}
onPointerDown={(e) => e.stopPropagation()}
data-testid={`hide-${feature.id}`}
title={feature.hidden ? 'Show' : 'Hide'}
>
<EyeOff className="w-3 h-3" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The icon for the hide/show button is static and always shows EyeOff. This can be confusing for the user. When the feature is hidden, the button's title is 'Show', so the icon should be Eye to represent the action of showing. When the feature is visible, the title is 'Hide', and the EyeOff icon is appropriate.

Consider making the icon dynamic based on the feature.hidden state to improve user experience. This would also make it consistent with the hide/show toggle in the backlog header.

Suggested change
<EyeOff className="w-3 h-3" />
{feature.hidden ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}

</Button>
)}
{feature.planSpec?.content && onViewPlan && (
<Button
variant="outline"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface KanbanCardProps {
onComplete?: () => void;
onViewPlan?: () => void;
onApprovePlan?: () => void;
onHide?: () => void;
hasContext?: boolean;
isCurrentAutoTask?: boolean;
shortcutKey?: string;
Expand All @@ -51,6 +52,7 @@ export const KanbanCard = memo(function KanbanCard({
onComplete,
onViewPlan,
onApprovePlan,
onHide,
hasContext,
isCurrentAutoTask,
shortcutKey,
Expand Down Expand Up @@ -177,6 +179,7 @@ export const KanbanCard = memo(function KanbanCard({
onComplete={onComplete}
onViewPlan={onViewPlan}
onApprovePlan={onApprovePlan}
onHide={onHide}
/>
</CardContent>
</Card>
Expand All @@ -187,5 +190,14 @@ export const KanbanCard = memo(function KanbanCard({
return <div className="animated-border-wrapper">{cardElement}</div>;
}

// Wrap hidden cards with visual indicator (dimmed + grayscale)
if (feature.hidden) {
return (
<div className="opacity-40 grayscale hover:opacity-70 hover:grayscale-[50%] transition-all duration-200">
{cardElement}
</div>
);
}

return cardElement;
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo } from 'react';
import { memo, useRef, useState, useEffect } from 'react';
import { useDroppable } from '@dnd-kit/core';
import { cn } from '@/lib/utils';
import { ChevronUp } from 'lucide-react';
import type { ReactNode } from 'react';

interface KanbanColumnProps {
Expand All @@ -10,11 +11,16 @@ interface KanbanColumnProps {
count: number;
children: ReactNode;
headerAction?: ReactNode;
footerAction?: ReactNode;
opacity?: number;
showBorder?: boolean;
hideScrollbar?: boolean;
/** Custom width in pixels. If not provided, defaults to 288px (w-72) */
width?: number;
/** Called when user clicks the compact bar to scroll to top */
onCompact?: () => void;
/** Number of currently visible items (used in compact bar label) */
visibleCount?: number;
}

export const KanbanColumn = memo(function KanbanColumn({
Expand All @@ -24,12 +30,38 @@ export const KanbanColumn = memo(function KanbanColumn({
count,
children,
headerAction,
footerAction,
opacity = 100,
showBorder = true,
hideScrollbar = false,
width,
onCompact,
visibleCount,
}: KanbanColumnProps) {
const { setNodeRef, isOver } = useDroppable({ id });
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [showCompactBar, setShowCompactBar] = useState(false);

// Track scroll position to show/hide compact bar
useEffect(() => {
const container = scrollContainerRef.current;
if (!container || !onCompact) return;

const handleScroll = () => {
// Show compact bar when scrolled more than 100px
setShowCompactBar(container.scrollTop > 100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The scroll threshold 100 is a magic number. It's better to define it as a constant at the top of the file or component for better readability and maintainability. For example:

const COMPACT_BAR_SCROLL_THRESHOLD_PX = 100;

Then use this constant here.

Suggested change
setShowCompactBar(container.scrollTop > 100);
setShowCompactBar(container.scrollTop > COMPACT_BAR_SCROLL_THRESHOLD_PX);

};

container.addEventListener('scroll', handleScroll, { passive: true });
return () => container.removeEventListener('scroll', handleScroll);
}, [onCompact]);

const handleCompactClick = () => {
// Scroll to top
scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
// Reset pagination
onCompact?.();
};

// Use inline style for width if provided, otherwise use default w-72
const widthStyle = width ? { width: `${width}px`, flexShrink: 0 } : undefined;
Expand Down Expand Up @@ -74,6 +106,7 @@ export const KanbanColumn = memo(function KanbanColumn({

{/* Column Content */}
<div
ref={scrollContainerRef}
className={cn(
'relative z-10 flex-1 overflow-y-auto p-2 space-y-2.5',
hideScrollbar &&
Expand All @@ -82,7 +115,29 @@ export const KanbanColumn = memo(function KanbanColumn({
'scroll-smooth'
)}
>
{/* Compact Bar - appears when scrolled down */}
{showCompactBar && onCompact && (
<div
className={cn(
'sticky top-0 z-20 -mx-2 -mt-2 mb-2 px-3 py-2',
'flex items-center justify-center gap-2',
'bg-primary/10 backdrop-blur-md',
'border-b border-primary/20',
'cursor-pointer',
'hover:bg-primary/20 transition-colors duration-200',
'text-xs font-medium text-primary'
)}
onClick={handleCompactClick}
data-testid="compact-bar"
>
<ChevronUp className="w-3.5 h-3.5" />
<span>Compact{visibleCount ? ` (${visibleCount})` : ''}</span>
<ChevronUp className="w-3.5 h-3.5" />
</div>
)}
Comment on lines +118 to +137
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Compact bar needs accessibility improvements.

The compact bar is implemented as a clickable div, which lacks keyboard navigation and proper semantic meaning. This prevents keyboard-only users from accessing the functionality.

Recommended accessibility fix
-       {showCompactBar && onCompact && (
-         <div
-           className={cn(
-             'sticky top-0 z-20 -mx-2 -mt-2 mb-2 px-3 py-2',
-             'flex items-center justify-center gap-2',
-             'bg-primary/10 backdrop-blur-md',
-             'border-b border-primary/20',
-             'cursor-pointer',
-             'hover:bg-primary/20 transition-colors duration-200',
-             'text-xs font-medium text-primary'
-           )}
-           onClick={handleCompactClick}
-           data-testid="compact-bar"
-         >
-           <ChevronUp className="w-3.5 h-3.5" />
-           <span>Compact{visibleCount ? ` (${visibleCount})` : ''}</span>
-           <ChevronUp className="w-3.5 h-3.5" />
-         </div>
-       )}
+       {showCompactBar && onCompact && (
+         <button
+           type="button"
+           className={cn(
+             'sticky top-0 z-20 -mx-2 -mt-2 mb-2 px-3 py-2 w-[calc(100%+1rem)]',
+             'flex items-center justify-center gap-2',
+             'bg-primary/10 backdrop-blur-md',
+             'border-b border-primary/20',
+             'cursor-pointer',
+             'hover:bg-primary/20 transition-colors duration-200',
+             'text-xs font-medium text-primary'
+           )}
+           onClick={handleCompactClick}
+           aria-label={`Scroll to top and reset view${visibleCount ? ` (showing ${visibleCount})` : ''}`}
+           data-testid="compact-bar"
+         >
+           <ChevronUp className="w-3.5 h-3.5" />
+           <span>Compact{visibleCount ? ` (${visibleCount})` : ''}</span>
+           <ChevronUp className="w-3.5 h-3.5" />
+         </button>
+       )}

This makes the compact bar keyboard accessible and provides proper semantic meaning with an aria-label.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Compact Bar - appears when scrolled down */}
{showCompactBar && onCompact && (
<div
className={cn(
'sticky top-0 z-20 -mx-2 -mt-2 mb-2 px-3 py-2',
'flex items-center justify-center gap-2',
'bg-primary/10 backdrop-blur-md',
'border-b border-primary/20',
'cursor-pointer',
'hover:bg-primary/20 transition-colors duration-200',
'text-xs font-medium text-primary'
)}
onClick={handleCompactClick}
data-testid="compact-bar"
>
<ChevronUp className="w-3.5 h-3.5" />
<span>Compact{visibleCount ? ` (${visibleCount})` : ''}</span>
<ChevronUp className="w-3.5 h-3.5" />
</div>
)}
{/* Compact Bar - appears when scrolled down */}
{showCompactBar && onCompact && (
<button
type="button"
className={cn(
'sticky top-0 z-20 -mx-2 -mt-2 mb-2 px-3 py-2 w-[calc(100%+1rem)]',
'flex items-center justify-center gap-2',
'bg-primary/10 backdrop-blur-md',
'border-b border-primary/20',
'cursor-pointer',
'hover:bg-primary/20 transition-colors duration-200',
'text-xs font-medium text-primary'
)}
onClick={handleCompactClick}
aria-label={`Scroll to top and reset view${visibleCount ? ` (showing ${visibleCount})` : ''}`}
data-testid="compact-bar"
>
<ChevronUp className="w-3.5 h-3.5" />
<span>Compact{visibleCount ? ` (${visibleCount})` : ''}</span>
<ChevronUp className="w-3.5 h-3.5" />
</button>
)}

{children}
{/* Footer Action (e.g., Show More button) */}
{footerAction && <div className="pt-1">{footerAction}</div>}
</div>

{/* Drop zone indicator when dragging over */}
Expand Down
17 changes: 17 additions & 0 deletions apps/ui/src/components/views/board-view/hooks/use-board-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,22 @@ export function useBoardActions({
});
}, [features, runningAutoTasks, autoMode, updateFeature, persistFeatureUpdate]);

const handleHideFeature = useCallback(
(feature: Feature) => {
const newHidden = !feature.hidden;
const updates = { hidden: newHidden };
updateFeature(feature.id, updates);
persistFeatureUpdate(feature.id, updates);

toast.success(newHidden ? 'Feature hidden' : 'Feature unhidden', {
description: newHidden
? `Hidden: ${truncateDescription(feature.description)}`
: `Shown: ${truncateDescription(feature.description)}`,
});
},
[updateFeature, persistFeatureUpdate]
);

return {
handleAddFeature,
handleUpdateFeature,
Expand All @@ -879,5 +895,6 @@ export function useBoardActions({
handleForceStopFeature,
handleStartNextFeatures,
handleArchiveAllVerified,
handleHideFeature,
};
}
Loading
Loading