Skip to content
Merged
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
3 changes: 3 additions & 0 deletions apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"@xyflow/react": "^12.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dagre": "^0.8.5",
"dotenv": "^17.2.3",
"geist": "^1.5.1",
"lucide-react": "^0.562.0",
Expand Down Expand Up @@ -95,6 +97,7 @@
"@playwright/test": "^1.57.0",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/router-plugin": "^1.141.7",
"@types/dagre": "^0.7.53",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat apps/ui/package.json | grep -A 5 -B 5 "dagre"

Repository: AutoMaker-Org/automaker

Length of output: 766


Version mismatch: @types/dagre 0.7.53 does not match dagre 0.8.5.

The type definitions are for dagre 0.7, but the project uses dagre 0.8.5. There are no published @types/dagre versions for the 0.8.x series. Consider either downgrading dagre to a 0.7.x version or finding an alternative type definition source.

🤖 Prompt for AI Agents
In apps/ui/package.json around line 100, the devDependency "@types/dagre":
"0.7.53" does not match the runtime dependency dagre 0.8.5; fix by making the
versions compatible: either downgrade dagre to a 0.7.x release to match the
installed @types, or remove the @types/dagre entry and add appropriate type
support for 0.8.x (for example add a local declaration file like declare module
"dagre" or install/point to an alternative type package) and then run npm/yarn
install to update lockfile; update package.json accordingly.

"@types/node": "^22",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
Expand Down
81 changes: 49 additions & 32 deletions apps/ui/src/components/views/board-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { BoardHeader } from './board-view/board-header';
import { BoardSearchBar } from './board-view/board-search-bar';
import { BoardControls } from './board-view/board-controls';
import { KanbanBoard } from './board-view/kanban-board';
import { GraphView } from './graph-view';
import {
AddFeatureDialog,
AgentOutputModal,
Expand Down Expand Up @@ -69,6 +70,8 @@ export function BoardView() {
aiProfiles,
kanbanCardDetailLevel,
setKanbanCardDetailLevel,
boardViewMode,
setBoardViewMode,
specCreatingForProject,
setSpecCreatingForProject,
pendingPlanApproval,
Expand Down Expand Up @@ -989,40 +992,54 @@ export function BoardView() {
completedCount={completedFeatures.length}
kanbanCardDetailLevel={kanbanCardDetailLevel}
onDetailLevelChange={setKanbanCardDetailLevel}
boardViewMode={boardViewMode}
onBoardViewModeChange={setBoardViewMode}
/>
</div>
{/* Kanban Columns */}
<KanbanBoard
sensors={sensors}
collisionDetectionStrategy={collisionDetectionStrategy}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
activeFeature={activeFeature}
getColumnFeatures={getColumnFeatures}
backgroundImageStyle={backgroundImageStyle}
backgroundSettings={backgroundSettings}
onEdit={(feature) => setEditingFeature(feature)}
onDelete={(featureId) => handleDeleteFeature(featureId)}
onViewOutput={handleViewOutput}
onVerify={handleVerifyFeature}
onResume={handleResumeFeature}
onForceStop={handleForceStopFeature}
onManualVerify={handleManualVerify}
onMoveBackToInProgress={handleMoveBackToInProgress}
onFollowUp={handleOpenFollowUp}
onCommit={handleCommitFeature}
onComplete={handleCompleteFeature}
onImplement={handleStartImplementation}
onViewPlan={(feature) => setViewPlanFeature(feature)}
onApprovePlan={handleOpenApprovalDialog}
featuresWithContext={featuresWithContext}
runningAutoTasks={runningAutoTasks}
shortcuts={shortcuts}
onStartNextFeatures={handleStartNextFeatures}
onShowSuggestions={() => setShowSuggestionsDialog(true)}
suggestionsCount={suggestionsCount}
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
/>
{/* View Content - Kanban or Graph */}
{boardViewMode === 'kanban' ? (
<KanbanBoard
sensors={sensors}
collisionDetectionStrategy={collisionDetectionStrategy}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
activeFeature={activeFeature}
getColumnFeatures={getColumnFeatures}
backgroundImageStyle={backgroundImageStyle}
backgroundSettings={backgroundSettings}
onEdit={(feature) => setEditingFeature(feature)}
onDelete={(featureId) => handleDeleteFeature(featureId)}
onViewOutput={handleViewOutput}
onVerify={handleVerifyFeature}
onResume={handleResumeFeature}
onForceStop={handleForceStopFeature}
onManualVerify={handleManualVerify}
onMoveBackToInProgress={handleMoveBackToInProgress}
onFollowUp={handleOpenFollowUp}
onCommit={handleCommitFeature}
onComplete={handleCompleteFeature}
onImplement={handleStartImplementation}
onViewPlan={(feature) => setViewPlanFeature(feature)}
onApprovePlan={handleOpenApprovalDialog}
featuresWithContext={featuresWithContext}
runningAutoTasks={runningAutoTasks}
shortcuts={shortcuts}
onStartNextFeatures={handleStartNextFeatures}
onShowSuggestions={() => setShowSuggestionsDialog(true)}
suggestionsCount={suggestionsCount}
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
/>
) : (
<GraphView
features={hookFeatures}
runningAutoTasks={runningAutoTasks}
currentWorktreePath={currentWorktreePath}
currentWorktreeBranch={currentWorktreeBranch}
projectPath={currentProject?.path || null}
onEditFeature={(feature) => setEditingFeature(feature)}
onViewOutput={handleViewOutput}
/>
)}
</div>

{/* Board Background Modal */}
Expand Down
52 changes: 51 additions & 1 deletion apps/ui/src/components/views/board-view/board-controls.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { ImageIcon, Archive, Minimize2, Square, Maximize2 } from 'lucide-react';
import { ImageIcon, Archive, Minimize2, Square, Maximize2, Columns3, Network } from 'lucide-react';
import { cn } from '@/lib/utils';
import { BoardViewMode } from '@/store/app-store';

interface BoardControlsProps {
isMounted: boolean;
Expand All @@ -10,6 +11,8 @@ interface BoardControlsProps {
completedCount: number;
kanbanCardDetailLevel: 'minimal' | 'standard' | 'detailed';
onDetailLevelChange: (level: 'minimal' | 'standard' | 'detailed') => void;
boardViewMode: BoardViewMode;
onBoardViewModeChange: (mode: BoardViewMode) => void;
}

export function BoardControls({
Expand All @@ -19,12 +22,59 @@ export function BoardControls({
completedCount,
kanbanCardDetailLevel,
onDetailLevelChange,
boardViewMode,
onBoardViewModeChange,
}: BoardControlsProps) {
if (!isMounted) return null;

return (
<TooltipProvider>
<div className="flex items-center gap-2 ml-4">
{/* View Mode Toggle - Kanban / Graph */}
<div
className="flex items-center rounded-lg bg-secondary border border-border"
data-testid="view-mode-toggle"
>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => onBoardViewModeChange('kanban')}
className={cn(
'p-2 rounded-l-lg transition-colors',
boardViewMode === 'kanban'
? 'bg-brand-500/20 text-brand-500'
: 'text-muted-foreground hover:text-foreground hover:bg-accent'
)}
data-testid="view-mode-kanban"
>
<Columns3 className="w-4 h-4" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>Kanban Board View</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => onBoardViewModeChange('graph')}
className={cn(
'p-2 rounded-r-lg transition-colors',
boardViewMode === 'graph'
? 'bg-brand-500/20 text-brand-500'
: 'text-muted-foreground hover:text-foreground hover:bg-accent'
)}
data-testid="view-mode-graph"
>
<Network className="w-4 h-4" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>Dependency Graph View</p>
</TooltipContent>
</Tooltip>
</div>

{/* Board Background Button */}
<Tooltip>
<TooltipTrigger asChild>
Expand Down
115 changes: 115 additions & 0 deletions apps/ui/src/components/views/graph-view/components/dependency-edge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { memo } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting issues flagged by CI.

The pipeline indicates code style issues found by Prettier. Run prettier --write on this file to fix formatting.

🧰 Tools
🪛 GitHub Actions: Format Check

[warning] 1-1: Code style issues found by Prettier. Run 'prettier --write' to fix this file.

🤖 Prompt for AI Agents
In apps/ui/src/components/views/graph-view/components/dependency-edge.tsx around
line 1, Prettier formatting violations were reported; run your project's
Prettier formatter (e.g., run prettier --write
apps/ui/src/components/views/graph-view/components/dependency-edge.tsx) or apply
the repo's formatting rules to this file so import spacing, semicolons, and line
breaks match the configured style, then stage the updated file and push the
changes.

import { BaseEdge, getBezierPath, EdgeLabelRenderer } from '@xyflow/react';
import type { EdgeProps } from '@xyflow/react';
import { cn } from '@/lib/utils';
import { Feature } from '@/store/app-store';

export interface DependencyEdgeData {
sourceStatus: Feature['status'];
targetStatus: Feature['status'];
}

const getEdgeColor = (sourceStatus?: Feature['status'], targetStatus?: Feature['status']) => {
// If source is completed/verified, the dependency is satisfied
if (sourceStatus === 'completed' || sourceStatus === 'verified') {
return 'var(--status-success)';
}
// If target is in progress, show active color
if (targetStatus === 'in_progress') {
return 'var(--status-in-progress)';
}
// If target is blocked (in backlog with incomplete deps)
if (targetStatus === 'backlog') {
return 'var(--border)';
}
// Default
return 'var(--border)';
};

export const DependencyEdge = memo(function DependencyEdge(props: EdgeProps) {
const {
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
data,
selected,
animated,
} = props;

const edgeData = data as DependencyEdgeData | undefined;

const [edgePath, labelX, labelY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
curvature: 0.25,
});

const edgeColor = edgeData
? getEdgeColor(edgeData.sourceStatus, edgeData.targetStatus)
: 'var(--border)';

const isCompleted = edgeData?.sourceStatus === 'completed' || edgeData?.sourceStatus === 'verified';
const isInProgress = edgeData?.targetStatus === 'in_progress';

return (
<>
{/* Background edge for better visibility */}
<BaseEdge
id={`${id}-bg`}
path={edgePath}
style={{
strokeWidth: 4,
stroke: 'var(--background)',
}}
/>

{/* Main edge */}
<BaseEdge
id={id}
path={edgePath}
className={cn(
'transition-all duration-300',
animated && 'animated-edge',
isInProgress && 'edge-flowing'
)}
style={{
strokeWidth: selected ? 3 : 2,
stroke: edgeColor,
strokeDasharray: isCompleted ? 'none' : '5 5',
filter: selected ? 'drop-shadow(0 0 3px var(--brand-500))' : 'none',
}}
/>

{/* Animated particles for in-progress edges */}
{animated && (
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
pointerEvents: 'none',
}}
className="edge-particle"
>
<div
className={cn(
'w-2 h-2 rounded-full',
isInProgress
? 'bg-[var(--status-in-progress)] animate-ping'
: 'bg-brand-500 animate-pulse'
)}
/>
</div>
</EdgeLabelRenderer>
)}
</>
);
});
Loading
Loading