# Conflicts:
#	app/src/app/page.tsx
This commit is contained in:
Kacper
2025-12-10 19:18:17 +01:00
24 changed files with 1198 additions and 206 deletions

View File

@@ -154,29 +154,31 @@ export function AgentOutputModal({
case "auto_mode_ultrathink_preparation":
// Format thinking level preparation information
let prepContent = `\n🧠 Ultrathink Preparation\n`;
if (event.warnings && event.warnings.length > 0) {
prepContent += `\n⚠ Warnings:\n`;
event.warnings.forEach((warning: string) => {
prepContent += `${warning}\n`;
});
}
if (event.recommendations && event.recommendations.length > 0) {
prepContent += `\n💡 Recommendations:\n`;
event.recommendations.forEach((rec: string) => {
prepContent += `${rec}\n`;
});
}
if (event.estimatedCost !== undefined) {
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(2)} per execution\n`;
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(
2
)} per execution\n`;
}
if (event.estimatedTime) {
prepContent += `\n⏱ Estimated Time: ${event.estimatedTime}\n`;
}
newContent = prepContent;
break;
case "auto_mode_feature_complete":
@@ -299,7 +301,7 @@ export function AgentOutputModal({
</DialogHeader>
{viewMode === "changes" ? (
<div className="flex-1 overflow-y-auto min-h-[400px] max-h-[60vh]">
<div className="flex-1 min-h-[400px] max-h-[60vh] overflow-y-auto scrollbar-visible">
{projectPath ? (
<GitDiffPanel
projectPath={projectPath}
@@ -320,7 +322,7 @@ export function AgentOutputModal({
<div
ref={scrollRef}
onScroll={handleScroll}
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh]"
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh] scrollbar-visible"
>
{isLoading && !output ? (
<div className="flex items-center justify-center h-full text-muted-foreground">

View File

@@ -268,6 +268,8 @@ export function BoardView() {
// Track previous project to detect switches
const prevProjectPathRef = useRef<string | null>(null);
const isSwitchingProjectRef = useRef<boolean>(false);
// Track if this is the initial load (to avoid showing loading spinner on subsequent reloads)
const isInitialLoadRef = useRef<boolean>(true);
// Auto mode hook
const autoMode = useAutoMode();
@@ -367,11 +369,13 @@ export function BoardView() {
const previousPath = prevProjectPathRef.current;
// If project switched, clear features first to prevent cross-contamination
// Also treat this as an initial load for the new project
if (previousPath !== null && currentPath !== previousPath) {
console.log(
`[BoardView] Project switch detected: ${previousPath} -> ${currentPath}, clearing features`
);
isSwitchingProjectRef.current = true;
isInitialLoadRef.current = true;
setFeatures([]);
setPersistedCategories([]); // Also clear categories
}
@@ -379,7 +383,11 @@ export function BoardView() {
// Update the ref to track current project
prevProjectPathRef.current = currentPath;
setIsLoading(true);
// Only show loading spinner on initial load to prevent board flash during reloads
if (isInitialLoadRef.current) {
setIsLoading(true);
}
try {
const api = getElectronAPI();
const result = await api.readFile(
@@ -403,6 +411,7 @@ export function BoardView() {
console.error("Failed to load features:", error);
} finally {
setIsLoading(false);
isInitialLoadRef.current = false;
isSwitchingProjectRef.current = false;
}
}, [currentProject, setFeatures]);
@@ -1270,17 +1279,35 @@ export function BoardView() {
}
};
const getColumnFeatures = (columnId: ColumnId) => {
return features.filter((f) => {
// Memoize column features to prevent unnecessary re-renders
const columnFeaturesMap = useMemo(() => {
const map: Record<ColumnId, Feature[]> = {
backlog: [],
in_progress: [],
waiting_approval: [],
verified: [],
};
features.forEach((f) => {
// If feature has a running agent, always show it in "in_progress"
const isRunning = runningAutoTasks.includes(f.id);
if (isRunning) {
return columnId === "in_progress";
map.in_progress.push(f);
} else {
// Otherwise, use the feature's status
map[f.status].push(f);
}
// Otherwise, use the feature's status
return f.status === columnId;
});
};
return map;
}, [features, runningAutoTasks]);
const getColumnFeatures = useCallback(
(columnId: ColumnId) => {
return columnFeaturesMap[columnId];
},
[columnFeaturesMap]
);
const handleViewOutput = (feature: Feature) => {
setOutputFeature(feature);
@@ -1537,7 +1564,7 @@ export function BoardView() {
<Plus className="w-4 h-4 mr-2" />
Add Feature
<span
className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-accent border border-border-glass"
className="ml-3 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground inline-flex items-center justify-center"
data-testid="shortcut-add-feature"
>
{ACTION_SHORTCUTS.addFeature}
@@ -1738,6 +1765,7 @@ export function BoardView() {
placeholder="Describe the feature..."
previewMap={newFeaturePreviewMap}
onPreviewMapChange={setNewFeaturePreviewMap}
autoFocus
/>
</div>
<div className="space-y-2">
@@ -2036,10 +2064,11 @@ export function BoardView() {
>
Add Feature
<span
className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/10 border border-primary-foreground/20"
className="ml-3 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/10 border border-primary-foreground/20 inline-flex items-center gap-1.5"
data-testid="shortcut-confirm-add-feature"
>
<span className="leading-none flex items-center justify-center"></span>
<span className="leading-none flex items-center justify-center"></span>
</span>
</Button>
</DialogFooter>

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, memo } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { cn } from "@/lib/utils";
@@ -92,7 +92,7 @@ interface KanbanCardProps {
summary?: string;
}
export function KanbanCard({
export const KanbanCard = memo(function KanbanCard({
feature,
onEdit,
onDelete,
@@ -227,7 +227,7 @@ export function KanbanCard({
{/* Shortcut key badge for in-progress cards */}
{shortcutKey && (
<div
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-muted border border-border text-muted-foreground z-10"
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70 z-10"
data-testid={`shortcut-key-${feature.id}`}
>
{shortcutKey}
@@ -869,4 +869,4 @@ export function KanbanCard({
</Dialog>
</Card>
);
}
});

View File

@@ -1,5 +1,6 @@
"use client";
import { memo } from "react";
import { useDroppable } from "@dnd-kit/core";
import { cn } from "@/lib/utils";
import type { ReactNode } from "react";
@@ -13,7 +14,7 @@ interface KanbanColumnProps {
headerAction?: ReactNode;
}
export function KanbanColumn({
export const KanbanColumn = memo(function KanbanColumn({
id,
title,
color,
@@ -48,4 +49,4 @@ export function KanbanColumn({
</div>
</div>
);
}
});

View File

@@ -548,7 +548,7 @@ export function ProfilesView() {
<Button onClick={() => setShowAddDialog(true)} data-testid="add-profile-button" className="relative">
<Plus className="w-4 h-4 mr-2" />
New Profile
<span className="hidden lg:flex items-center justify-center ml-2 px-2 py-0.5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-500">
<span className="hidden lg:flex items-center justify-center ml-2 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground">
{ACTION_SHORTCUTS.addProfile}
</span>
</Button>