diff --git a/.secretsignore b/.secretsignore new file mode 100644 index 0000000000..39c09ea3b5 --- /dev/null +++ b/.secretsignore @@ -0,0 +1,9 @@ +# Test files with mock credentials +*.test.ts +*.test.tsx +test_*.py +tests/ + +# Scripts with checksums/hashes (not secrets) +apps/frontend/scripts/download-python.cjs +.github/actions/ diff --git a/apps/frontend/src/renderer/App.tsx b/apps/frontend/src/renderer/App.tsx index 3e8eddcdef..84f107772a 100644 --- a/apps/frontend/src/renderer/App.tsx +++ b/apps/frontend/src/renderer/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Download, RefreshCw, AlertCircle } from 'lucide-react'; import { debugLog } from '../shared/utils/debug-logger'; @@ -70,7 +70,7 @@ import { COLOR_THEMES, UI_SCALE_MIN, UI_SCALE_MAX, UI_SCALE_DEFAULT } from '../s import type { Task, Project, ColorTheme } from '../shared/types'; import { ProjectTabBar } from './components/ProjectTabBar'; import { AddProjectModal } from './components/AddProjectModal'; -import { ViewStateProvider } from './contexts/ViewStateContext'; +import { ViewStateProvider, useViewState } from './contexts/ViewStateContext'; // Version constant for version-specific warnings (e.g., reauthentication notices) const VERSION_WARNING_275 = '2.7.5'; @@ -82,7 +82,9 @@ interface ProjectTabBarWithContextProps { onProjectSelect: (projectId: string) => void; onProjectClose: (projectId: string) => void; onAddProject: () => void; - onSettingsClick: () => void; + onRefresh?: () => void; + isRefreshing?: boolean; + tasks: Task[]; } function ProjectTabBarWithContext({ @@ -91,8 +93,18 @@ function ProjectTabBarWithContext({ onProjectSelect, onProjectClose, onAddProject, - onSettingsClick + onRefresh, + isRefreshing, + tasks }: ProjectTabBarWithContextProps) { + // Get showArchived from shared context for cross-view sync with Ideation + const { showArchived, toggleShowArchived } = useViewState(); + + // Calculate archived task count + const archivedCount = useMemo(() => tasks.filter( + (task) => task.metadata?.archivedAt + ).length, [tasks]); + return ( ); } @@ -824,6 +840,12 @@ export function App() { } }; + // Compute kanban-specific props once to avoid repetitive ternaries + const isKanban = activeView === 'kanban'; + const kanbanOnlyProps = isKanban + ? { onRefresh: handleRefreshTasks, isRefreshing: isRefreshingTasks } + : {}; + return ( @@ -854,7 +876,8 @@ export function App() { onProjectSelect={handleProjectTabSelect} onProjectClose={handleProjectTabClose} onAddProject={handleAddProject} - onSettingsClick={() => setIsSettingsDialogOpen(true)} + tasks={tasks} + {...kanbanOnlyProps} /> @@ -881,8 +904,6 @@ export function App() { tasks={tasks} onTaskClick={handleTaskClick} onNewTaskClick={() => setIsNewTaskDialogOpen(true)} - onRefresh={handleRefreshTasks} - isRefreshing={isRefreshingTasks} /> )} {/* TerminalGrid is always mounted but hidden when not active to preserve terminal state */} diff --git a/apps/frontend/src/renderer/components/KanbanBoard.tsx b/apps/frontend/src/renderer/components/KanbanBoard.tsx index 25e37d56a7..2115eb77b9 100644 --- a/apps/frontend/src/renderer/components/KanbanBoard.tsx +++ b/apps/frontend/src/renderer/components/KanbanBoard.tsx @@ -19,7 +19,7 @@ import { sortableKeyboardCoordinates, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw, GitPullRequest, X, Settings, ListPlus, ChevronLeft, ChevronRight, ChevronsRight, Lock, Unlock, Trash2 } from 'lucide-react'; +import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, GitPullRequest, X, Settings, ListPlus, ChevronLeft, ChevronRight, ChevronsRight, Lock, Unlock, Trash2 } from 'lucide-react'; import { Checkbox } from './ui/checkbox'; import { ScrollArea } from './ui/scroll-area'; import { Button } from './ui/button'; @@ -69,8 +69,6 @@ interface KanbanBoardProps { tasks: Task[]; onTaskClick: (task: Task) => void; onNewTaskClick?: () => void; - onRefresh?: () => void; - isRefreshing?: boolean; } interface DroppableColumnProps { @@ -84,9 +82,6 @@ interface DroppableColumnProps { onQueueSettings?: () => void; onQueueAll?: () => void; maxParallelTasks?: number; - archivedCount?: number; - showArchived?: boolean; - onToggleArchived?: () => void; // Selection props for human_review column selectedTaskIds?: Set; onSelectAll?: () => void; @@ -146,9 +141,6 @@ function droppableColumnPropsAreEqual( if (prevProps.onQueueSettings !== nextProps.onQueueSettings) return false; if (prevProps.onQueueAll !== nextProps.onQueueAll) return false; if (prevProps.maxParallelTasks !== nextProps.maxParallelTasks) return false; - if (prevProps.archivedCount !== nextProps.archivedCount) return false; - if (prevProps.showArchived !== nextProps.showArchived) return false; - if (prevProps.onToggleArchived !== nextProps.onToggleArchived) return false; if (prevProps.onSelectAll !== nextProps.onSelectAll) return false; if (prevProps.onDeselectAll !== nextProps.onDeselectAll) return false; if (prevProps.onToggleSelect !== nextProps.onToggleSelect) return false; @@ -230,11 +222,13 @@ const getEmptyStateContent = (status: TaskStatus, t: (key: string) => string): { } }; -const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, onStatusChange, isOver, onAddClick, onArchiveAll, onQueueSettings, onQueueAll, maxParallelTasks, archivedCount, showArchived, onToggleArchived, selectedTaskIds, onSelectAll, onDeselectAll, onToggleSelect, isCollapsed, onToggleCollapsed, columnWidth, isResizing, onResizeStart, onResizeEnd, isLocked, onToggleLocked }: DroppableColumnProps) { +const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, onStatusChange, isOver, onAddClick, onArchiveAll, onQueueSettings, onQueueAll, maxParallelTasks, selectedTaskIds, onSelectAll, onDeselectAll, onToggleSelect, isCollapsed, onToggleCollapsed, columnWidth, isResizing, onResizeStart, onResizeEnd, isLocked, onToggleLocked }: DroppableColumnProps) { const { t } = useTranslation(['tasks', 'common']); const { setNodeRef } = useDroppable({ id: status }); + // Get showArchived from shared context to conditionally show Archive All button + const { showArchived } = useViewState(); // Calculate selection state for this column const taskCount = tasks.length; @@ -524,33 +518,6 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli )} - {status === 'done' && archivedCount !== undefined && archivedCount > 0 && onToggleArchived && ( - - - - - - {showArchived ? t('common:projectTab.hideArchived') : t('common:projectTab.showArchived')} - - - )} @@ -634,12 +601,13 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli ); }, droppableColumnPropsAreEqual); -export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isRefreshing }: KanbanBoardProps) { +export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardProps) { const { t } = useTranslation(['tasks', 'dialogs', 'common']); const { toast } = useToast(); + // Get showArchived from shared context for cross-view sync with Ideation + const { showArchived } = useViewState(); const [activeTask, setActiveTask] = useState(null); const [overColumnId, setOverColumnId] = useState(null); - const { showArchived, toggleShowArchived } = useViewState(); // Project store for queue settings const projects = useProjectStore((state) => state.projects); @@ -700,12 +668,6 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR error: undefined }); - // Calculate archived count for Done column button - const archivedCount = useMemo(() => - tasks.filter(t => t.metadata?.archivedAt).length, - [tasks] - ); - // Calculate collapsed column count for "Expand All" button const collapsedColumnCount = useMemo(() => { if (!columnPreferences) return 0; @@ -1428,36 +1390,20 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR return (
- {/* Kanban header with refresh button and expand all */} - {(onRefresh || collapsedColumnCount >= 3) && ( + {/* Kanban header with expand all button */} + {collapsedColumnCount >= 3 && (
{/* Expand All button - appears when 3+ columns are collapsed */} - {collapsedColumnCount >= 3 && ( - - )} -
-
- {onRefresh && ( - - )} +
)} @@ -1488,9 +1434,6 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR } : undefined} onArchiveAll={status === 'done' ? handleArchiveAll : undefined} maxParallelTasks={status === 'in_progress' ? maxParallelTasks : undefined} - archivedCount={status === 'done' ? archivedCount : undefined} - showArchived={status === 'done' ? showArchived : undefined} - onToggleArchived={status === 'done' ? toggleShowArchived : undefined} selectedTaskIds={selectedTaskIds} onSelectAll={() => selectAllTasks(status)} onDeselectAll={deselectAllTasks} diff --git a/apps/frontend/src/renderer/components/ProjectTabBar.tsx b/apps/frontend/src/renderer/components/ProjectTabBar.tsx index d76b34f463..0d85171f25 100644 --- a/apps/frontend/src/renderer/components/ProjectTabBar.tsx +++ b/apps/frontend/src/renderer/components/ProjectTabBar.tsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Plus } from 'lucide-react'; +import { Plus, RefreshCw, Archive } from 'lucide-react'; import { cn } from '../lib/utils'; import { Button } from './ui/button'; +import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { SortableProjectTab } from './SortableProjectTab'; import { UsageIndicator } from './UsageIndicator'; import { AuthStatusIndicator } from './AuthStatusIndicator'; @@ -15,8 +16,12 @@ interface ProjectTabBarProps { onProjectClose: (projectId: string) => void; onAddProject: () => void; className?: string; - // Control props for active tab - onSettingsClick?: () => void; + // Kanban board controls (only displayed when Kanban view is active) + onRefresh?: () => void; + isRefreshing?: boolean; + showArchived?: boolean; + onToggleArchived?: () => void; + archivedCount?: number; } export function ProjectTabBar({ @@ -26,9 +31,13 @@ export function ProjectTabBar({ onProjectClose, onAddProject, className, - onSettingsClick + onRefresh, + isRefreshing = false, + showArchived = false, + onToggleArchived, + archivedCount = 0 }: ProjectTabBarProps) { - const { t } = useTranslation('common'); + const { t } = useTranslation(['common', 'tasks']); // Keyboard shortcuts for tab navigation useEffect(() => { @@ -86,11 +95,12 @@ export function ProjectTabBar({ return (
-
+ {/* Left side: Project tabs + Add Project button */} +
{projects.map((project, index) => { const isActiveTab = activeProjectId === project.id; return ( @@ -105,25 +115,83 @@ export function ProjectTabBar({ e.stopPropagation(); onProjectClose(project.id); }} - // Pass control props only for active tab - onSettingsClick={isActiveTab ? onSettingsClick : undefined} /> ); })} + + {/* Add project button with tooltip - stays next to tabs */} + + + + + + {t('common:projectTab.addNewProject')} + +
-
+ {/* Right side: Auth + Usage indicators + Refresh + Show Archived - anchored to right */} +
- + + {/* Refresh button */} + {onRefresh && ( + + + + + + {t('common:projectTab.refreshTasks')} + + + )} + + {/* Show Archived button */} + {onToggleArchived && archivedCount > 0 && ( + + + + + + {showArchived ? t('common:projectTab.hideArchived') : t('common:projectTab.showArchived')} + + + )}
); diff --git a/apps/frontend/src/renderer/components/SortableProjectTab.tsx b/apps/frontend/src/renderer/components/SortableProjectTab.tsx index d57cf1292c..2ca360d5ef 100644 --- a/apps/frontend/src/renderer/components/SortableProjectTab.tsx +++ b/apps/frontend/src/renderer/components/SortableProjectTab.tsx @@ -1,7 +1,6 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useTranslation } from 'react-i18next'; -import { Settings2 } from 'lucide-react'; import { cn } from '../lib/utils'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import type { Project } from '../../shared/types'; @@ -13,8 +12,6 @@ interface SortableProjectTabProps { tabIndex: number; onSelect: () => void; onClose: (e: React.MouseEvent) => void; - // Optional control props for active tab - onSettingsClick?: () => void; } // Detect if running on macOS for keyboard shortcut display @@ -27,8 +24,7 @@ export function SortableProjectTab({ canClose, tabIndex, onSelect, - onClose, - onSettingsClick + onClose }: SortableProjectTabProps) { const { t } = useTranslation('common'); // Build tooltip with keyboard shortcut hint (only for tabs 1-9) @@ -112,39 +108,6 @@ export function SortableProjectTab({ - {/* Active tab controls - settings and archive, always accessible */} - {isActive && ( -
- {/* Settings icon - responsive sizing */} - {onSettingsClick && ( - - - - - - {t('projectTab.settings')} - - - )} -
- )} - {canClose && ( diff --git a/apps/frontend/src/shared/i18n/locales/en/common.json b/apps/frontend/src/shared/i18n/locales/en/common.json index 6a2f1d84f2..189821e99d 100644 --- a/apps/frontend/src/shared/i18n/locales/en/common.json +++ b/apps/frontend/src/shared/i18n/locales/en/common.json @@ -25,7 +25,9 @@ "hideArchivedTasks": "Hide archived tasks", "closeTab": "Close tab", "closeTabAriaLabel": "Close tab (removes project from app)", - "addProjectAriaLabel": "Add project" + "addProjectAriaLabel": "Add project", + "refreshTasks": "Refresh tasks", + "addNewProject": "Add new project" }, "accessibility": { "deleteFeatureAriaLabel": "Delete feature", diff --git a/apps/frontend/src/shared/i18n/locales/fr/common.json b/apps/frontend/src/shared/i18n/locales/fr/common.json index 113736f227..a25b2f2f24 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/common.json +++ b/apps/frontend/src/shared/i18n/locales/fr/common.json @@ -25,7 +25,9 @@ "hideArchivedTasks": "Masquer les tâches archivées", "closeTab": "Fermer l'onglet", "closeTabAriaLabel": "Fermer l'onglet (retire le projet de l'application)", - "addProjectAriaLabel": "Ajouter un projet" + "addProjectAriaLabel": "Ajouter un projet", + "refreshTasks": "Actualiser les tâches", + "addNewProject": "Ajouter un nouveau projet" }, "accessibility": { "deleteFeatureAriaLabel": "Supprimer la fonctionnalité", diff --git a/package-lock.json b/package-lock.json index 31ab465ad8..c9dc697958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "auto-claude", - "version": "2.7.6-beta.6", + "version": "2.7.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "auto-claude", - "version": "2.7.6-beta.6", + "version": "2.7.6", "license": "AGPL-3.0", "workspaces": [ "apps/*", @@ -25,7 +25,7 @@ }, "apps/frontend": { "name": "auto-claude-ui", - "version": "2.7.6-beta.6", + "version": "2.7.6", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": {