mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
feat: enhance board background settings and introduce animated borders
- Added default background settings to streamline background management across components. - Implemented animated border styles for in-progress cards to improve visual feedback. - Refactored BoardBackgroundModal and BoardView components to utilize the new default settings, ensuring consistent background behavior. - Updated KanbanCard to support animated borders, enhancing the user experience during task progress. - Improved Sidebar component by optimizing the fetching of running agents count with a more efficient use of hooks.
This commit is contained in:
@@ -1397,6 +1397,39 @@
|
|||||||
.text-running-indicator {
|
.text-running-indicator {
|
||||||
color: var(--running-indicator-text);
|
color: var(--running-indicator-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Animated border for in-progress cards */
|
||||||
|
@keyframes border-rotate {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated-border-wrapper {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--running-indicator),
|
||||||
|
color-mix(in oklch, var(--running-indicator), transparent 50%),
|
||||||
|
var(--running-indicator),
|
||||||
|
color-mix(in oklch, var(--running-indicator), transparent 50%),
|
||||||
|
var(--running-indicator)
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: border-rotate 3s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated-border-wrapper > * {
|
||||||
|
border-radius: calc(0.75rem - 2px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Retro Overrides for Utilities */
|
/* Retro Overrides for Utilities */
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Slider } from "@/components/ui/slider";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore, defaultBackgroundSettings } from "@/store/app-store";
|
||||||
import { getHttpApiClient } from "@/lib/http-api-client";
|
import { getHttpApiClient } from "@/lib/http-api-client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -55,27 +55,9 @@ export function BoardBackgroundModal({
|
|||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Get current background settings (live from store)
|
// Get current background settings (live from store)
|
||||||
const backgroundSettings = currentProject
|
const backgroundSettings =
|
||||||
? boardBackgroundByProject[currentProject.path] || {
|
(currentProject && boardBackgroundByProject[currentProject.path]) ||
|
||||||
imagePath: null,
|
defaultBackgroundSettings;
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const cardOpacity = backgroundSettings.cardOpacity;
|
const cardOpacity = backgroundSettings.cardOpacity;
|
||||||
const columnOpacity = backgroundSettings.columnOpacity;
|
const columnOpacity = backgroundSettings.columnOpacity;
|
||||||
@@ -84,6 +66,7 @@ export function BoardBackgroundModal({
|
|||||||
const cardBorderEnabled = backgroundSettings.cardBorderEnabled;
|
const cardBorderEnabled = backgroundSettings.cardBorderEnabled;
|
||||||
const cardBorderOpacity = backgroundSettings.cardBorderOpacity;
|
const cardBorderOpacity = backgroundSettings.cardBorderOpacity;
|
||||||
const hideScrollbar = backgroundSettings.hideScrollbar;
|
const hideScrollbar = backgroundSettings.hideScrollbar;
|
||||||
|
const imageVersion = backgroundSettings.imageVersion;
|
||||||
|
|
||||||
// Update preview image when background settings change
|
// Update preview image when background settings change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -91,8 +74,8 @@ export function BoardBackgroundModal({
|
|||||||
const serverUrl =
|
const serverUrl =
|
||||||
process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3008";
|
process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3008";
|
||||||
// Add cache-busting query parameter to force browser to reload image
|
// Add cache-busting query parameter to force browser to reload image
|
||||||
const cacheBuster = backgroundSettings.imageVersion
|
const cacheBuster = imageVersion
|
||||||
? `&v=${backgroundSettings.imageVersion}`
|
? `&v=${imageVersion}`
|
||||||
: `&v=${Date.now()}`;
|
: `&v=${Date.now()}`;
|
||||||
const imagePath = `${serverUrl}/api/fs/image?path=${encodeURIComponent(
|
const imagePath = `${serverUrl}/api/fs/image?path=${encodeURIComponent(
|
||||||
backgroundSettings.imagePath
|
backgroundSettings.imagePath
|
||||||
@@ -101,11 +84,7 @@ export function BoardBackgroundModal({
|
|||||||
} else {
|
} else {
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
}
|
}
|
||||||
}, [
|
}, [currentProject, backgroundSettings.imagePath, imageVersion]);
|
||||||
currentProject,
|
|
||||||
backgroundSettings.imagePath,
|
|
||||||
backgroundSettings.imageVersion,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const fileToBase64 = (file: File): Promise<string> => {
|
const fileToBase64 = (file: File): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -332,9 +332,8 @@ export function Sidebar() {
|
|||||||
};
|
};
|
||||||
}, [setCurrentView]);
|
}, [setCurrentView]);
|
||||||
|
|
||||||
// Fetch running agents count and update every 2 seconds
|
// Fetch running agents count function - used for initial load and event-driven updates
|
||||||
useEffect(() => {
|
const fetchRunningAgentsCount = useCallback(async () => {
|
||||||
const fetchRunningAgentsCount = async () => {
|
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (api.runningAgents) {
|
if (api.runningAgents) {
|
||||||
@@ -346,21 +345,19 @@ export function Sidebar() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Sidebar] Error fetching running agents count:", error);
|
console.error("[Sidebar] Error fetching running agents count:", error);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Initial fetch
|
|
||||||
fetchRunningAgentsCount();
|
|
||||||
|
|
||||||
// Set up interval to refresh every 2 seconds
|
|
||||||
const interval = setInterval(fetchRunningAgentsCount, 2000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Subscribe to auto-mode events to update running agents count in real-time
|
// Subscribe to auto-mode events to update running agents count in real-time
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api.autoMode) return;
|
if (!api.autoMode) {
|
||||||
|
// If autoMode is not available, still fetch initial count
|
||||||
|
fetchRunningAgentsCount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial fetch on mount
|
||||||
|
fetchRunningAgentsCount();
|
||||||
|
|
||||||
const unsubscribe = api.autoMode.onEvent((event) => {
|
const unsubscribe = api.autoMode.onEvent((event) => {
|
||||||
// When a feature starts, completes, or errors, refresh the count
|
// When a feature starts, completes, or errors, refresh the count
|
||||||
@@ -369,21 +366,6 @@ export function Sidebar() {
|
|||||||
event.type === "auto_mode_error" ||
|
event.type === "auto_mode_error" ||
|
||||||
event.type === "auto_mode_feature_start"
|
event.type === "auto_mode_feature_start"
|
||||||
) {
|
) {
|
||||||
const fetchRunningAgentsCount = async () => {
|
|
||||||
try {
|
|
||||||
if (api.runningAgents) {
|
|
||||||
const result = await api.runningAgents.getAll();
|
|
||||||
if (result.success && result.runningAgents) {
|
|
||||||
setRunningAgentsCount(result.runningAgents.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"[Sidebar] Error fetching running agents count:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchRunningAgentsCount();
|
fetchRunningAgentsCount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -391,7 +373,7 @@ export function Sidebar() {
|
|||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [fetchRunningAgentsCount]);
|
||||||
|
|
||||||
// Handle creating initial spec for new project
|
// Handle creating initial spec for new project
|
||||||
const handleCreateInitialSpec = useCallback(async () => {
|
const handleCreateInitialSpec = useCallback(async () => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
AgentModel,
|
AgentModel,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
AIProfile,
|
AIProfile,
|
||||||
|
defaultBackgroundSettings,
|
||||||
} from "@/store/app-store";
|
} from "@/store/app-store";
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
import { cn, modelSupportsThinking } from "@/lib/utils";
|
import { cn, modelSupportsThinking } from "@/lib/utils";
|
||||||
@@ -1554,25 +1555,6 @@ export function BoardView() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort waiting_approval column: justFinished features (within 2 minutes) go to the top
|
|
||||||
map.waiting_approval.sort((a, b) => {
|
|
||||||
// Helper to check if feature is "just finished" (within 2 minutes)
|
|
||||||
const isJustFinished = (feature: Feature) => {
|
|
||||||
if (!feature.justFinishedAt) return false;
|
|
||||||
const finishedTime = new Date(feature.justFinishedAt).getTime();
|
|
||||||
const now = Date.now();
|
|
||||||
const twoMinutes = 2 * 60 * 1000; // 2 minutes in milliseconds
|
|
||||||
return now - finishedTime < twoMinutes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const aJustFinished = isJustFinished(a);
|
|
||||||
const bJustFinished = isJustFinished(b);
|
|
||||||
// Features with justFinishedAt within 2 minutes should appear first
|
|
||||||
if (aJustFinished && !bJustFinished) return -1;
|
|
||||||
if (!aJustFinished && bJustFinished) return 1;
|
|
||||||
return 0; // Keep original order for features with same justFinished status
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}, [features, runningAutoTasks, searchQuery]);
|
}, [features, runningAutoTasks, searchQuery]);
|
||||||
|
|
||||||
@@ -1975,27 +1957,9 @@ export function BoardView() {
|
|||||||
{/* Kanban Columns */}
|
{/* Kanban Columns */}
|
||||||
{(() => {
|
{(() => {
|
||||||
// Get background settings for current project
|
// Get background settings for current project
|
||||||
const backgroundSettings = currentProject
|
const backgroundSettings =
|
||||||
? boardBackgroundByProject[currentProject.path] || {
|
(currentProject && boardBackgroundByProject[currentProject.path]) ||
|
||||||
imagePath: null,
|
defaultBackgroundSettings;
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build background image style if image exists
|
// Build background image style if image exists
|
||||||
const backgroundImageStyle = backgroundSettings.imagePath
|
const backgroundImageStyle = backgroundSettings.imagePath
|
||||||
|
|||||||
@@ -309,26 +309,30 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
).borderColor = `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`;
|
).borderColor = `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const cardElement = (
|
||||||
<Card
|
<Card
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={borderStyle}
|
style={isCurrentAutoTask ? style : borderStyle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-grab active:cursor-grabbing transition-all relative kanban-card-content select-none",
|
"cursor-grab active:cursor-grabbing transition-all relative kanban-card-content select-none",
|
||||||
// Apply border class when border is enabled and opacity is 100%
|
// Apply border class when border is enabled and opacity is 100%
|
||||||
// When opacity is not 100%, we use inline styles for border color
|
// When opacity is not 100%, we use inline styles for border color
|
||||||
cardBorderEnabled && cardBorderOpacity === 100 && "border-border",
|
// Skip border classes when animated border is active (isCurrentAutoTask)
|
||||||
|
!isCurrentAutoTask &&
|
||||||
|
cardBorderEnabled &&
|
||||||
|
cardBorderOpacity === 100 &&
|
||||||
|
"border-border",
|
||||||
// When border is enabled but opacity is not 100%, we still need border width
|
// When border is enabled but opacity is not 100%, we still need border width
|
||||||
cardBorderEnabled && cardBorderOpacity !== 100 && "border",
|
!isCurrentAutoTask &&
|
||||||
|
cardBorderEnabled &&
|
||||||
|
cardBorderOpacity !== 100 &&
|
||||||
|
"border",
|
||||||
// Remove default background when using opacity overlay
|
// Remove default background when using opacity overlay
|
||||||
!isDragging && "bg-transparent",
|
!isDragging && "bg-transparent",
|
||||||
// Remove default backdrop-blur-sm from Card component when glassmorphism is disabled
|
// Remove default backdrop-blur-sm from Card component when glassmorphism is disabled
|
||||||
!glassmorphism && "backdrop-blur-[0px]!",
|
!glassmorphism && "backdrop-blur-[0px]!",
|
||||||
isDragging && "scale-105 shadow-lg",
|
isDragging && "scale-105 shadow-lg",
|
||||||
// Special border styles for running/error states override the border opacity
|
// Error state border (only when not in progress)
|
||||||
// These need to be applied with higher specificity
|
|
||||||
isCurrentAutoTask &&
|
|
||||||
"border-running-indicator border-2 shadow-running-indicator/50 shadow-lg animate-pulse",
|
|
||||||
feature.error &&
|
feature.error &&
|
||||||
!isCurrentAutoTask &&
|
!isCurrentAutoTask &&
|
||||||
"border-red-500 border-2 shadow-red-500/30 shadow-lg",
|
"border-red-500 border-2 shadow-red-500/30 shadow-lg",
|
||||||
@@ -1056,4 +1060,11 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Wrap with animated border when in progress
|
||||||
|
if (isCurrentAutoTask) {
|
||||||
|
return <div className="animated-border-wrapper">{cardElement}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardElement;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -388,6 +388,28 @@ export interface AppState {
|
|||||||
previewTheme: ThemeMode | null;
|
previewTheme: ThemeMode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default background settings for board backgrounds
|
||||||
|
export const defaultBackgroundSettings: {
|
||||||
|
imagePath: string | null;
|
||||||
|
imageVersion?: number;
|
||||||
|
cardOpacity: number;
|
||||||
|
columnOpacity: number;
|
||||||
|
columnBorderEnabled: boolean;
|
||||||
|
cardGlassmorphism: boolean;
|
||||||
|
cardBorderEnabled: boolean;
|
||||||
|
cardBorderOpacity: number;
|
||||||
|
hideScrollbar: boolean;
|
||||||
|
} = {
|
||||||
|
imagePath: null,
|
||||||
|
cardOpacity: 100,
|
||||||
|
columnOpacity: 100,
|
||||||
|
columnBorderEnabled: true,
|
||||||
|
cardGlassmorphism: true,
|
||||||
|
cardBorderEnabled: true,
|
||||||
|
cardBorderOpacity: 100,
|
||||||
|
hideScrollbar: false,
|
||||||
|
};
|
||||||
|
|
||||||
export interface AutoModeActivity {
|
export interface AutoModeActivity {
|
||||||
id: string;
|
id: string;
|
||||||
featureId: string;
|
featureId: string;
|
||||||
@@ -1345,16 +1367,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setCardOpacity: (projectPath, opacity) => {
|
setCardOpacity: (projectPath, opacity) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1368,16 +1381,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setColumnOpacity: (projectPath, opacity) => {
|
setColumnOpacity: (projectPath, opacity) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1391,32 +1395,12 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
getBoardBackground: (projectPath) => {
|
getBoardBackground: (projectPath) => {
|
||||||
const settings = get().boardBackgroundByProject[projectPath];
|
const settings = get().boardBackgroundByProject[projectPath];
|
||||||
return (
|
return settings || defaultBackgroundSettings;
|
||||||
settings || {
|
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setColumnBorderEnabled: (projectPath, enabled) => {
|
setColumnBorderEnabled: (projectPath, enabled) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1430,16 +1414,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setCardGlassmorphism: (projectPath, enabled) => {
|
setCardGlassmorphism: (projectPath, enabled) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1453,16 +1428,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setCardBorderEnabled: (projectPath, enabled) => {
|
setCardBorderEnabled: (projectPath, enabled) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1476,16 +1442,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setCardBorderOpacity: (projectPath, opacity) => {
|
setCardBorderOpacity: (projectPath, opacity) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1499,16 +1456,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
setHideScrollbar: (projectPath, hide) => {
|
setHideScrollbar: (projectPath, hide) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
@@ -1522,16 +1470,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
|
|
||||||
clearBoardBackground: (projectPath) => {
|
clearBoardBackground: (projectPath) => {
|
||||||
const current = get().boardBackgroundByProject;
|
const current = get().boardBackgroundByProject;
|
||||||
const existing = current[projectPath] || {
|
const existing = current[projectPath] || defaultBackgroundSettings;
|
||||||
imagePath: null,
|
|
||||||
cardOpacity: 100,
|
|
||||||
columnOpacity: 100,
|
|
||||||
columnBorderEnabled: true,
|
|
||||||
cardGlassmorphism: true,
|
|
||||||
cardBorderEnabled: true,
|
|
||||||
cardBorderOpacity: 100,
|
|
||||||
hideScrollbar: false,
|
|
||||||
};
|
|
||||||
set({
|
set({
|
||||||
boardBackgroundByProject: {
|
boardBackgroundByProject: {
|
||||||
...current,
|
...current,
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ const execAsync = promisify(exec);
|
|||||||
|
|
||||||
interface Feature {
|
interface Feature {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
category: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
steps?: string[];
|
||||||
status: string;
|
status: string;
|
||||||
priority?: number;
|
priority?: number;
|
||||||
spec?: string;
|
spec?: string;
|
||||||
@@ -636,7 +637,9 @@ Address the follow-up instructions above. Review the previous work and make the
|
|||||||
// Load feature for commit message
|
// Load feature for commit message
|
||||||
const feature = await this.loadFeature(projectPath, featureId);
|
const feature = await this.loadFeature(projectPath, featureId);
|
||||||
const commitMessage = feature
|
const commitMessage = feature
|
||||||
? `feat: ${feature.title}\n\nImplemented by Automaker auto-mode`
|
? `feat: ${this.extractTitleFromDescription(
|
||||||
|
feature.description
|
||||||
|
)}\n\nImplemented by Automaker auto-mode`
|
||||||
: `feat: Feature ${featureId}`;
|
: `feat: Feature ${featureId}`;
|
||||||
|
|
||||||
// Stage and commit
|
// Stage and commit
|
||||||
@@ -930,11 +933,56 @@ Format your response as a structured markdown document.`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a title from feature description (first line or truncated)
|
||||||
|
*/
|
||||||
|
private extractTitleFromDescription(description: string): string {
|
||||||
|
if (!description || !description.trim()) {
|
||||||
|
return "Untitled Feature";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get first line, or first 60 characters if no newline
|
||||||
|
const firstLine = description.split("\n")[0].trim();
|
||||||
|
if (firstLine.length <= 60) {
|
||||||
|
return firstLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate to 60 characters and add ellipsis
|
||||||
|
return firstLine.substring(0, 57) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract image paths from feature's imagePaths array
|
||||||
|
* Handles both string paths and objects with path property
|
||||||
|
*/
|
||||||
|
private extractImagePaths(
|
||||||
|
imagePaths:
|
||||||
|
| Array<string | { path: string; [key: string]: unknown }>
|
||||||
|
| undefined,
|
||||||
|
projectPath: string
|
||||||
|
): string[] {
|
||||||
|
if (!imagePaths || imagePaths.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return imagePaths
|
||||||
|
.map((imgPath) => {
|
||||||
|
const pathStr = typeof imgPath === "string" ? imgPath : imgPath.path;
|
||||||
|
// Resolve relative paths to absolute paths
|
||||||
|
return path.isAbsolute(pathStr)
|
||||||
|
? pathStr
|
||||||
|
: path.join(projectPath, pathStr);
|
||||||
|
})
|
||||||
|
.filter((p) => p); // Filter out any empty paths
|
||||||
|
}
|
||||||
|
|
||||||
private buildFeaturePrompt(feature: Feature): string {
|
private buildFeaturePrompt(feature: Feature): string {
|
||||||
|
const title = this.extractTitleFromDescription(feature.description);
|
||||||
|
|
||||||
let prompt = `## Feature Implementation Task
|
let prompt = `## Feature Implementation Task
|
||||||
|
|
||||||
**Feature ID:** ${feature.id}
|
**Feature ID:** ${feature.id}
|
||||||
**Title:** ${feature.title}
|
**Title:** ${title}
|
||||||
**Description:** ${feature.description}
|
**Description:** ${feature.description}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user