diff --git a/apps/ui/src/app.tsx b/apps/ui/src/app.tsx index b2cb1525..dedd9e8d 100644 --- a/apps/ui/src/app.tsx +++ b/apps/ui/src/app.tsx @@ -7,6 +7,7 @@ import { useSettingsSync } from './hooks/use-settings-sync'; import { useCursorStatusInit } from './hooks/use-cursor-status-init'; import { useProviderAuthInit } from './hooks/use-provider-auth-init'; import { useAppStore } from './store/app-store'; +import { TooltipProvider } from '@/components/ui/tooltip'; import './styles/global.css'; import './styles/theme-imports'; import './styles/font-imports'; @@ -75,9 +76,9 @@ export default function App() { }, []); return ( - <> + {showSplash && !disableSplashScreen && } - + ); } diff --git a/apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx b/apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx index 0dab1694..666d2339 100644 --- a/apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx +++ b/apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx @@ -5,7 +5,7 @@ import { formatShortcut } from '@/store/app-store'; import { Activity, Settings, BookOpen, MessageSquare, ExternalLink } from 'lucide-react'; import { useOSDetection } from '@/hooks/use-os-detection'; import { getElectronAPI } from '@/lib/electron'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; function getOSAbbreviation(os: string): string { switch (os) { @@ -72,68 +72,14 @@ export function SidebarFooter({
{/* Running Agents */} {!hideRunningAgents && ( - - - - - - - Running Agents - {runningAgentsCount > 0 && ( - - {runningAgentsCount} - - )} - - - - )} - - {/* Settings */} - - Global Settings - - {formatShortcut(shortcuts.settings, true)} - + Running Agents + {runningAgentsCount > 0 && ( + + {runningAgentsCount} + + )} - + )} + + {/* Settings */} + + + + + + Global Settings + + {formatShortcut(shortcuts.settings, true)} + + + {/* Documentation */} {!hideWiki && ( - - - - - - - Documentation - - - - )} - - {/* Feedback */} - - Feedback + Documentation - + )} + + {/* Feedback */} + + + + + + Feedback + +
); diff --git a/apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx b/apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx index a1360e79..7a92aec9 100644 --- a/apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx +++ b/apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx @@ -15,7 +15,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; interface SidebarHeaderProps { sidebarOpen: boolean; @@ -92,78 +92,74 @@ export function SidebarHeader({ isMac && isElectron() && 'pt-[10px]' )} > - - - - - - - Go to Dashboard - - - + + + + + + + + + + + + + + + Go to Dashboard + + {/* Collapsed project icon with dropdown */} {currentProject && ( <>
- - - - - - - - - {currentProject.name} - - - + + + + + + + + {currentProject.name} + + > = { @@ -158,27 +158,25 @@ export function SidebarNavigation({ {/* Section icon with dropdown (collapsed sidebar) */} {section.label && !sidebarOpen && SectionIcon && section.collapsible && isCollapsed && ( - - - - - - - - - {section.label} - - - + + + + + + + + {section.label} + + {section.items.map((item) => { const ItemIcon = item.icon; diff --git a/apps/ui/src/components/ui/keyboard-map.tsx b/apps/ui/src/components/ui/keyboard-map.tsx index 10de4edb..cc5c76bd 100644 --- a/apps/ui/src/components/ui/keyboard-map.tsx +++ b/apps/ui/src/components/ui/keyboard-map.tsx @@ -7,7 +7,7 @@ import { } from '@/store/app-store'; import type { KeyboardShortcuts } from '@/store/app-store'; import { cn } from '@/lib/utils'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { CheckCircle2, X, RotateCcw, Edit2 } from 'lucide-react'; @@ -305,54 +305,52 @@ export function KeyboardMap({ onKeySelect, selectedKey, className }: KeyboardMap }; return ( - -
- {/* Legend */} -
- {Object.entries(CATEGORY_COLORS).map(([key, colors]) => ( -
-
- {colors.label} -
- ))} -
-
- Available -
-
-
- Modified +
+ {/* Legend */} +
+ {Object.entries(CATEGORY_COLORS).map(([key, colors]) => ( +
+
+ {colors.label}
+ ))} +
+
+ Available
- - {/* Keyboard layout */} -
- {KEYBOARD_ROWS.map((row, rowIndex) => ( -
- {row.map(renderKey)} -
- ))} -
- - {/* Stats */} -
- - {Object.keys(keyboardShortcuts).length}{' '} - shortcuts configured - - - {Object.keys(keyToShortcuts).length} keys - in use - - - - {KEYBOARD_ROWS.flat().length - Object.keys(keyToShortcuts).length} - {' '} - keys available - +
+
+ Modified
- + + {/* Keyboard layout */} +
+ {KEYBOARD_ROWS.map((row, rowIndex) => ( +
+ {row.map(renderKey)} +
+ ))} +
+ + {/* Stats */} +
+ + {Object.keys(keyboardShortcuts).length}{' '} + shortcuts configured + + + {Object.keys(keyToShortcuts).length} keys in + use + + + + {KEYBOARD_ROWS.flat().length - Object.keys(keyToShortcuts).length} + {' '} + keys available + +
+
); } @@ -508,196 +506,194 @@ export function ShortcutReferencePanel({ editable = false }: ShortcutReferencePa }; return ( - -
- {editable && ( -
- -
- )} - {Object.entries(groupedShortcuts).map(([category, shortcuts]) => { - const colors = CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS]; - return ( -
-

{colors.label}

-
- {shortcuts.map(({ key, label, value }) => { - const isModified = mergedShortcuts[key] !== DEFAULT_KEYBOARD_SHORTCUTS[key]; - const isEditing = editingShortcut === key; +
+ {editable && ( +
+ +
+ )} + {Object.entries(groupedShortcuts).map(([category, shortcuts]) => { + const colors = CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS]; + return ( +
+

{colors.label}

+
+ {shortcuts.map(({ key, label, value }) => { + const isModified = mergedShortcuts[key] !== DEFAULT_KEYBOARD_SHORTCUTS[key]; + const isEditing = editingShortcut === key; - return ( -
editable && !isEditing && handleStartEdit(key)} - data-testid={`shortcut-row-${key}`} - > - {label} -
- {isEditing ? ( -
e.stopPropagation()} - > - {/* Modifier checkboxes */} -
-
- - handleModifierChange('cmdCtrl', !!checked, key) - } - className="h-3.5 w-3.5" - /> - -
-
- - handleModifierChange('alt', !!checked, key) - } - className="h-3.5 w-3.5" - /> - -
-
- - handleModifierChange('shift', !!checked, key) - } - className="h-3.5 w-3.5" - /> - -
+ return ( +
editable && !isEditing && handleStartEdit(key)} + data-testid={`shortcut-row-${key}`} + > + {label} +
+ {isEditing ? ( +
e.stopPropagation()} + > + {/* Modifier checkboxes */} +
+
+ + handleModifierChange('cmdCtrl', !!checked, key) + } + className="h-3.5 w-3.5" + /> + +
+
+ + handleModifierChange('alt', !!checked, key) + } + className="h-3.5 w-3.5" + /> + +
+
+ + handleModifierChange('shift', !!checked, key) + } + className="h-3.5 w-3.5" + /> +
- + - handleKeyChange(e.target.value, key)} - onKeyDown={handleKeyDown} - className={cn( - 'w-12 h-7 text-center font-mono text-xs uppercase', - shortcutError && 'border-red-500 focus-visible:ring-red-500' - )} - placeholder="Key" - maxLength={1} - autoFocus - data-testid={`edit-shortcut-input-${key}`} - /> - -
- ) : ( - <> - - {formatShortcut(value, true)} - - {isModified && editable && ( - - - - - - Reset to default ({DEFAULT_KEYBOARD_SHORTCUTS[key]}) - - + + + handleKeyChange(e.target.value, key)} + onKeyDown={handleKeyDown} + className={cn( + 'w-12 h-7 text-center font-mono text-xs uppercase', + shortcutError && 'border-red-500 focus-visible:ring-red-500' )} - {isModified && !editable && ( - + placeholder="Key" + maxLength={1} + autoFocus + data-testid={`edit-shortcut-input-${key}`} + /> + + +
+ ) : ( + <> + - )} - - )} -
+ > + {formatShortcut(value, true)} + + {isModified && editable && ( + + + + + + Reset to default ({DEFAULT_KEYBOARD_SHORTCUTS[key]}) + + + )} + {isModified && !editable && ( + + )} + {editable && !isModified && ( + + )} + + )}
- ); - })} -
- {editingShortcut && - shortcutError && - SHORTCUT_CATEGORIES[editingShortcut] === category && ( -

{shortcutError}

- )} +
+ ); + })}
- ); - })} -
- + {editingShortcut && + shortcutError && + SHORTCUT_CATEGORIES[editingShortcut] === category && ( +

{shortcutError}

+ )} +
+ ); + })} +
); } diff --git a/apps/ui/src/components/views/board-view/board-controls.tsx b/apps/ui/src/components/views/board-view/board-controls.tsx index 8584bbdb..2baa7ceb 100644 --- a/apps/ui/src/components/views/board-view/board-controls.tsx +++ b/apps/ui/src/components/views/board-view/board-controls.tsx @@ -1,4 +1,4 @@ -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { ImageIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -18,24 +18,22 @@ export function BoardControls({ isMounted, onShowBoardBackground }: BoardControl ); return ( - -
- {/* Board Background Button */} - - - - - -

Board Background Settings

-
-
-
-
+
+ {/* Board Background Button */} + + + + + +

Board Background Settings

+
+
+
); } diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx index 4563bc06..90b709c2 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx @@ -2,7 +2,7 @@ import { memo, useEffect, useMemo, useState } from 'react'; import { Feature, useAppStore } from '@/store/app-store'; import { cn } from '@/lib/utils'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { AlertCircle, Lock, Hand, Sparkles, SkipForward } from 'lucide-react'; import { getBlockingDependencies } from '@automaker/dependency-resolver'; import { useShallow } from 'zustand/react/shallow'; @@ -28,24 +28,22 @@ export const CardBadges = memo(function CardBadges({ feature }: CardBadgesProps) return (
{/* Error badge */} - - - -
- -
-
- -

{feature.error}

-
-
-
+ + +
+ +
+
+ +

{feature.error}

+
+
); }); @@ -138,147 +136,137 @@ export const PriorityBadges = memo(function PriorityBadges({
{/* Priority badge */} {feature.priority && ( - - - -
- - {feature.priority === 1 ? 'H' : feature.priority === 2 ? 'M' : 'L'} - -
-
- -

- {feature.priority === 1 - ? 'High Priority' - : feature.priority === 2 - ? 'Medium Priority' - : 'Low Priority'} -

-
-
-
+ + +
+ + {feature.priority === 1 ? 'H' : feature.priority === 2 ? 'M' : 'L'} + +
+
+ +

+ {feature.priority === 1 + ? 'High Priority' + : feature.priority === 2 + ? 'Medium Priority' + : 'Low Priority'} +

+
+
)} {/* Manual verification badge */} {showManualVerification && ( - - - -
- -
-
- -

Manual verification required

-
-
-
+ + +
+ +
+
+ +

Manual verification required

+
+
)} {/* Blocked badge */} {isBlocked && ( - - - -
- -
-
- -

- Blocked by {blockingDependencies.length} incomplete{' '} - {blockingDependencies.length === 1 ? 'dependency' : 'dependencies'} -

-

- {blockingDependencies - .map((depId) => { - const dep = features.find((f) => f.id === depId); - return dep?.description || depId; - }) - .join(', ')} -

-
-
-
+ + +
+ +
+
+ +

+ Blocked by {blockingDependencies.length} incomplete{' '} + {blockingDependencies.length === 1 ? 'dependency' : 'dependencies'} +

+

+ {blockingDependencies + .map((depId) => { + const dep = features.find((f) => f.id === depId); + return dep?.description || depId; + }) + .join(', ')} +

+
+
)} {/* Just Finished badge */} {isJustFinished && ( - - - -
- -
-
- -

Agent just finished working on this feature

-
-
-
+ + +
+ +
+
+ +

Agent just finished working on this feature

+
+
)} {/* Pipeline exclusion badge */} {hasPipelineExclusions && ( - - - -
- -
-
- -

- {allPipelinesExcluded - ? 'All pipelines skipped' - : `${excludedStepCount} of ${totalPipelineSteps} pipeline${totalPipelineSteps !== 1 ? 's' : ''} skipped`} -

-

- {allPipelinesExcluded - ? 'This feature will skip all custom pipeline steps' - : 'Some custom pipeline steps will be skipped for this feature'} -

-
-
-
+ + +
+ +
+
+ +

+ {allPipelinesExcluded + ? 'All pipelines skipped' + : `${excludedStepCount} of ${totalPipelineSteps} pipeline${totalPipelineSteps !== 1 ? 's' : ''} skipped`} +

+

+ {allPipelinesExcluded + ? 'This feature will skip all custom pipeline steps' + : 'Some custom pipeline steps will be skipped for this feature'} +

+
+
)}
); diff --git a/apps/ui/src/components/views/board-view/components/kanban-column.tsx b/apps/ui/src/components/views/board-view/components/kanban-column.tsx index 1fc1029b..a280c955 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-column.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-column.tsx @@ -78,7 +78,9 @@ export const KanbanColumn = memo(function KanbanColumn({ )} >
-

{title}

+

+ {title} +

{headerAction} {count} diff --git a/apps/ui/src/components/views/board-view/components/list-view/list-header.tsx b/apps/ui/src/components/views/board-view/components/list-view/list-header.tsx index aad969b6..1c874caf 100644 --- a/apps/ui/src/components/views/board-view/components/list-view/list-header.tsx +++ b/apps/ui/src/components/views/board-view/components/list-view/list-header.tsx @@ -132,7 +132,7 @@ const SortableColumnHeader = memo(function SortableColumnHeader({ )} data-testid={`list-header-${column.id}`} > - {column.label} + {column.label}
); @@ -156,7 +156,7 @@ const StaticColumnHeader = memo(function StaticColumnHeader({ column }: { column )} data-testid={`list-header-${column.id}`} > - {column.label} + {column.label}
); }); diff --git a/apps/ui/src/components/views/board-view/components/list-view/list-row.tsx b/apps/ui/src/components/views/board-view/components/list-view/list-row.tsx index a3d10eb7..32b0f445 100644 --- a/apps/ui/src/components/views/board-view/components/list-view/list-row.tsx +++ b/apps/ui/src/components/views/board-view/components/list-view/list-row.tsx @@ -3,7 +3,7 @@ // @ts-nocheck import { memo, useCallback, useState, useEffect } from 'react'; import { cn } from '@/lib/utils'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { AlertCircle, Lock, Hand, Sparkles, FileText } from 'lucide-react'; import type { Feature } from '@/store/app-store'; import { RowActions, type RowActionHandlers } from './row-actions'; @@ -149,29 +149,27 @@ const IndicatorBadges = memo(function IndicatorBadges({ return (
- - {badges.map((badge) => ( - - -
- -
-
- -

{badge.tooltip}

-
-
- ))} -
+ {badges.map((badge) => ( + + +
+ +
+
+ +

{badge.tooltip}

+
+
+ ))}
); }); diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 490f6a5b..6fa66061 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -50,7 +50,7 @@ import { } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { getAncestors, formatAncestorContextForPrompt, @@ -532,26 +532,24 @@ export function AddFeatureDialog({ AI & Execution
- - - - - - -

Change default model and planning settings for new features

-
-
-
+ + + + + +

Change default model and planning settings for new features

+
+
diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 34baae2e..61a20878 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -41,7 +41,7 @@ import { } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { DependencyTreeDialog } from './dependency-tree-dialog'; import { supportsReasoningEffort } from '@automaker/types'; @@ -424,26 +424,24 @@ export function EditFeatureDialog({ AI & Execution
- - - - - - -

Change default model and planning settings for new features

-
-
-
+ + + + + +

Change default model and planning settings for new features

+
+
diff --git a/apps/ui/src/components/views/board-view/kanban-board.tsx b/apps/ui/src/components/views/board-view/kanban-board.tsx index 8314e74f..9da06723 100644 --- a/apps/ui/src/components/views/board-view/kanban-board.tsx +++ b/apps/ui/src/components/views/board-view/kanban-board.tsx @@ -12,7 +12,8 @@ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { Button } from '@/components/ui/button'; import { KanbanColumn, KanbanCard, EmptyStateCard } from './components'; import { Feature, useAppStore, formatShortcut } from '@/store/app-store'; -import { Archive, Settings2, CheckSquare, GripVertical, Plus } from 'lucide-react'; +import { Archive, Settings2, CheckSquare, GripVertical, Plus, CheckCircle2 } from 'lucide-react'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useResponsiveKanban } from '@/hooks/use-responsive-kanban'; import { getColumnsWithPipeline, type ColumnId } from './constants'; import type { PipelineConfig } from '@automaker/types'; @@ -359,32 +360,44 @@ export function KanbanBoard({ column.id === 'verified' ? (
{columnFeatures.length > 0 && ( - + + + + + +

Complete All

+
+
)} - + + + + + +

Completed Features ({completedCount})

+
+
) : column.id === 'backlog' ? (
diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/tooltip-wrapper.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/tooltip-wrapper.tsx index b1d9d8e7..40988894 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/tooltip-wrapper.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/tooltip-wrapper.tsx @@ -1,5 +1,5 @@ import type { ReactElement, ReactNode } from 'react'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; interface TooltipWrapperProps { /** The element to wrap with a tooltip */ @@ -29,16 +29,14 @@ export function TooltipWrapper({ } return ( - - - - {/* The div wrapper is necessary for tooltips to work on disabled elements */} -
{children}
-
- -

{tooltipContent}

-
-
-
+ + + {/* The div wrapper is necessary for tooltips to work on disabled elements */} +
{children}
+
+ +

{tooltipContent}

+
+
); } diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx index 8549c40b..2fb79aed 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown-item.tsx @@ -1,5 +1,5 @@ import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Check, CircleDot, Globe, GitPullRequest, FlaskConical } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; @@ -101,14 +101,12 @@ export function WorktreeDropdownItem({ {/* Branch name with optional tooltip */} {isBranchNameTruncated ? ( - - - {branchNameElement} - -

{worktree.branch}

-
-
-
+ + {branchNameElement} + +

{worktree.branch}

+
+
) : ( branchNameElement )} diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx index c2bf8ac5..fd1c2ba3 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx @@ -8,7 +8,7 @@ import { DropdownMenuTrigger, DropdownMenuGroup, } from '@/components/ui/dropdown-menu'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { GitBranch, ChevronDown, @@ -335,14 +335,12 @@ export function WorktreeDropdown({ const dropdownTrigger = {triggerButton}; const triggerWithTooltip = isBranchNameTruncated ? ( - - - {dropdownTrigger} - -

{displayBranch}

-
-
-
+ + {dropdownTrigger} + +

{displayBranch}

+
+
) : ( dropdownTrigger ); diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx index a4722406..9d508ed3 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx @@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button'; import { Globe, CircleDot, GitPullRequest } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useDroppable } from '@dnd-kit/core'; import type { WorktreeInfo, @@ -271,29 +271,27 @@ export function WorktreeTab({ )} {hasChanges && ( - - - - - - {changedFilesCount ?? '!'} - - - -

- {changedFilesCount ?? 'Some'} uncommitted file - {changedFilesCount !== 1 ? 's' : ''} -

-
-
-
+ + + + + {changedFilesCount ?? '!'} + + + +

+ {changedFilesCount ?? 'Some'} uncommitted file + {changedFilesCount !== 1 ? 's' : ''} +

+
+
)} {prBadge} @@ -340,78 +338,72 @@ export function WorktreeTab({ )} {hasChanges && ( - - - - - - {changedFilesCount ?? '!'} - - - -

- {changedFilesCount ?? 'Some'} uncommitted file - {changedFilesCount !== 1 ? 's' : ''} -

-
-
-
+ + + + + {changedFilesCount ?? '!'} + + + +

+ {changedFilesCount ?? 'Some'} uncommitted file + {changedFilesCount !== 1 ? 's' : ''} +

+
+
)} {prBadge} )} {isDevServerRunning && ( - - - - - - -

Open dev server (:{devServerInfo?.port})

-
-
-
+ + + + + +

Open dev server (:{devServerInfo?.port})

+
+
)} {isAutoModeRunning && ( - - - - - - - - -

Auto Mode Running

-
-
-
+ + + + + + + +

Auto Mode Running

+
+
)} - -
- {/* Zoom controls */} - - - - - Zoom In - +
+ {/* Zoom controls */} + + + + + Zoom In + - - - - - Zoom Out - + + + + + Zoom Out + - - - - - Fit View - + + + + + Fit View + -
+
- {/* Layout controls */} - - - - - Horizontal Layout - + {/* Layout controls */} + + + + + Horizontal Layout + - - - - - Vertical Layout - + + + + + Vertical Layout + -
+
- {/* Lock toggle */} - - - - - {isLocked ? 'Unlock Nodes' : 'Lock Nodes'} - -
- + {/* Lock toggle */} + + + + + {isLocked ? 'Unlock Nodes' : 'Lock Nodes'} + +
); } diff --git a/apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx b/apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx index 35af041b..16c9abf6 100644 --- a/apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx +++ b/apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx @@ -4,7 +4,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Switch } from '@/components/ui/switch'; import { Input } from '@/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Filter, X, @@ -115,248 +115,252 @@ export function GraphFilterControls({ return ( - -
- {/* Search Input */} -
- - onSearchQueryChange(e.target.value)} - className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50" - /> - {searchQuery && ( - - )} -
- - {/* Divider */} -
- - {/* Category Filter Dropdown */} - - - - - - - - Filter by Category - - -
-
- Categories -
- - {/* Select All option */} -
- 0 - } - onCheckedChange={handleSelectAllCategories} - /> - - {selectedCategories.length === availableCategories.length - ? 'Deselect All' - : 'Select All'} - -
- -
- - {/* Category list */} -
- {availableCategories.length === 0 ? ( -
- No categories available -
- ) : ( - availableCategories.map((category) => ( -
handleCategoryToggle(category)} - > - handleCategoryToggle(category)} - /> - {category} -
- )) - )} -
-
- - - - {/* Status Filter Dropdown */} - - - - - - - - Filter by Status - - -
-
Status
- - {/* Select All option */} -
- - - {selectedStatuses.length === STATUS_FILTER_OPTIONS.length - ? 'Deselect All' - : 'Select All'} - -
- -
- - {/* Status list */} -
- {STATUS_FILTER_OPTIONS.map((status) => { - const config = statusDisplayConfig[status]; - const StatusIcon = config.icon; - return ( -
handleStatusToggle(status)} - > - handleStatusToggle(status)} - /> - - {config.label} -
- ); - })} -
-
- - - - {/* Divider */} -
- - {/* Positive/Negative Filter Toggle */} - - -
- - -
-
- - {isNegativeFilter - ? 'Negative filter: Highlighting non-matching nodes' - : 'Positive filter: Highlighting matching nodes'} - -
- - {/* Clear Filters Button - only show when filters are active */} - {hasActiveFilter && ( - <> -
- - - - - Clear All Filters - - +
+ {/* Search Input */} +
+ + onSearchQueryChange(e.target.value)} + className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50" + /> + {searchQuery && ( + )}
- + + {/* Divider */} +
+ + {/* Category Filter Dropdown */} + + + + + + + + Filter by Category + + e.preventDefault()} + > +
+
Categories
+ + {/* Select All option */} +
+ 0 + } + onCheckedChange={handleSelectAllCategories} + /> + + {selectedCategories.length === availableCategories.length + ? 'Deselect All' + : 'Select All'} + +
+ +
+ + {/* Category list */} +
+ {availableCategories.length === 0 ? ( +
+ No categories available +
+ ) : ( + availableCategories.map((category) => ( +
handleCategoryToggle(category)} + > + handleCategoryToggle(category)} + /> + {category} +
+ )) + )} +
+
+ + + + {/* Status Filter Dropdown */} + + + + + + + + Filter by Status + + e.preventDefault()} + > +
+
Status
+ + {/* Select All option */} +
+ + + {selectedStatuses.length === STATUS_FILTER_OPTIONS.length + ? 'Deselect All' + : 'Select All'} + +
+ +
+ + {/* Status list */} +
+ {STATUS_FILTER_OPTIONS.map((status) => { + const config = statusDisplayConfig[status]; + const StatusIcon = config.icon; + return ( +
handleStatusToggle(status)} + > + handleStatusToggle(status)} + /> + + {config.label} +
+ ); + })} +
+
+ + + + {/* Divider */} +
+ + {/* Positive/Negative Filter Toggle */} + + +
+ + +
+
+ + {isNegativeFilter + ? 'Negative filter: Highlighting non-matching nodes' + : 'Positive filter: Highlighting matching nodes'} + +
+ + {/* Clear Filters Button - only show when filters are active */} + {hasActiveFilter && ( + <> +
+ + + + + Clear All Filters + + + )} +
); } diff --git a/apps/ui/src/components/views/graph-view/components/task-node.tsx b/apps/ui/src/components/views/graph-view/components/task-node.tsx index 16cf6817..98b95c46 100644 --- a/apps/ui/src/components/views/graph-view/components/task-node.tsx +++ b/apps/ui/src/components/views/graph-view/components/task-node.tsx @@ -26,7 +26,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; type TaskNodeProps = NodeProps & { data: TaskNodeData; @@ -286,50 +286,44 @@ export const TaskNode = memo(function TaskNode({ data, selected }: TaskNodeProps {/* Blocked indicator */} {data.isBlocked && !data.error && data.status === 'backlog' && ( - - - -
- -
-
- -

Blocked by {data.blockingDependencies.length} dependencies

-
-
-
+ + +
+ +
+
+ +

Blocked by {data.blockingDependencies.length} dependencies

+
+
)} {/* Error indicator */} {data.error && ( - - - -
- -
-
- -

{data.error}

-
-
-
+ + +
+ +
+
+ +

{data.error}

+
+
)} {/* Stopped indicator - task is in_progress but not actively running */} {isStopped && ( - - - -
- -
-
- -

Task paused - click menu to resume

-
-
-
+ + +
+ +
+
+ +

Task paused - click menu to resume

+
+
)} {/* Actions dropdown */} diff --git a/apps/ui/src/components/views/settings-view/account/account-section.tsx b/apps/ui/src/components/views/settings-view/account/account-section.tsx index d10049fc..abacd8ee 100644 --- a/apps/ui/src/components/views/settings-view/account/account-section.tsx +++ b/apps/ui/src/components/views/settings-view/account/account-section.tsx @@ -8,7 +8,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { toast } from 'sonner'; import { LogOut, User, Code2, RefreshCw } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; @@ -134,24 +134,22 @@ export function AccountSection() { })} - - - - - - -

Refresh available editors

-
-
-
+ + + + + +

Refresh available editors

+
+
diff --git a/apps/ui/src/hooks/use-responsive-kanban.ts b/apps/ui/src/hooks/use-responsive-kanban.ts index 3062e715..3c1c5efb 100644 --- a/apps/ui/src/hooks/use-responsive-kanban.ts +++ b/apps/ui/src/hooks/use-responsive-kanban.ts @@ -14,8 +14,8 @@ export interface ResponsiveKanbanConfig { * Default configuration for responsive Kanban columns */ const DEFAULT_CONFIG: ResponsiveKanbanConfig = { - columnWidth: 288, // 18rem = 288px (w-72) - columnMinWidth: 280, // Minimum column width - ensures usability + columnWidth: 320, // Increased from 288px to accommodate longer column titles + columnMinWidth: 320, // Increased from 280px to prevent title overflow columnMaxWidth: Infinity, // No max width - columns scale evenly to fill viewport gap: 20, // gap-5 = 20px padding: 40, // px-5 on both sides = 40px (matches gap between columns) diff --git a/apps/ui/src/lib/agent-context-parser.ts b/apps/ui/src/lib/agent-context-parser.ts index 35e2cceb..8313e055 100644 --- a/apps/ui/src/lib/agent-context-parser.ts +++ b/apps/ui/src/lib/agent-context-parser.ts @@ -237,39 +237,34 @@ function cleanFragmentedText(content: string): string { /** * Extracts a summary from completed feature context * Looks for content between and tags + * Returns the LAST summary found to ensure we get the most recent/updated one */ function extractSummary(content: string): string | undefined { // First, clean up any fragmented text from streaming const cleanedContent = cleanFragmentedText(content); - // Look for tags - capture everything between opening and closing tags - const summaryTagMatch = cleanedContent.match(/([\s\S]*?)<\/summary>/i); - if (summaryTagMatch) { - // Clean up the extracted summary content as well - return cleanFragmentedText(summaryTagMatch[1]).trim(); - } + // Define regex patterns to try in order of priority + // Each pattern specifies which capture group contains the summary content + const regexesToTry = [ + { regex: /([\s\S]*?)<\/summary>/gi, group: 1 }, + { regex: /## Summary[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/gi, group: 1 }, + { + regex: + /✓ (?:Feature|Verification|Task) (?:successfully|completed|verified)[^\n]*(?:\n[^\n]{1,200})?/gi, + group: 0, + }, + { + regex: /(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/gi, + group: 1, + }, + ]; - // Fallback: Look for summary sections - capture everything including subsections (###) - // Stop at same-level ## sections (but not ###), or tool markers, or end - const summaryMatch = cleanedContent.match(/## Summary[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i); - if (summaryMatch) { - return cleanFragmentedText(summaryMatch[1]).trim(); - } - - // Look for completion markers and extract surrounding text - const completionMatch = cleanedContent.match( - /✓ (?:Feature|Verification|Task) (?:successfully|completed|verified)[^\n]*(?:\n[^\n]{1,200})?/i - ); - if (completionMatch) { - return cleanFragmentedText(completionMatch[0]).trim(); - } - - // Look for "What was done" type sections - const whatWasDoneMatch = cleanedContent.match( - /(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i - ); - if (whatWasDoneMatch) { - return cleanFragmentedText(whatWasDoneMatch[1]).trim(); + for (const { regex, group } of regexesToTry) { + const matches = [...cleanedContent.matchAll(regex)]; + if (matches.length > 0) { + const lastMatch = matches[matches.length - 1]; + return cleanFragmentedText(lastMatch[group]).trim(); + } } return undefined; diff --git a/apps/ui/src/lib/log-parser.ts b/apps/ui/src/lib/log-parser.ts index 8a873b5f..5228a8fc 100644 --- a/apps/ui/src/lib/log-parser.ts +++ b/apps/ui/src/lib/log-parser.ts @@ -1198,46 +1198,48 @@ function mergeConsecutiveEntries(entries: LogEntry[]): LogEntry[] { /** * Extracts summary content from raw log output - * Returns the summary text if found, or null if no summary exists + * Returns the LAST summary text if found, or null if no summary exists + * This ensures we get the most recent/updated summary when multiple exist */ export function extractSummary(rawOutput: string): string | null { if (!rawOutput || !rawOutput.trim()) { return null; } - // Try to find tags first (preferred format) - const summaryTagMatch = rawOutput.match(/([\s\S]*?)<\/summary>/); - if (summaryTagMatch) { - return summaryTagMatch[1].trim(); - } + // First, clean up any fragmented text from streaming + // This handles cases where streaming providers send partial text chunks + // that got separated by newlines during accumulation (e.g., "") + const cleanedOutput = cleanFragmentedText(rawOutput); - // Try to find markdown ## Summary section - const summaryHeaderMatch = rawOutput.match(/^##\s+Summary\s*\n([\s\S]*?)(?=\n##\s+|$)/m); - if (summaryHeaderMatch) { - return summaryHeaderMatch[1].trim(); - } + // Define regex patterns to try in order of priority + // Each pattern specifies a processor function to extract the summary from the match + const regexesToTry: Array<{ + regex: RegExp; + processor: (m: RegExpMatchArray) => string; + }> = [ + { regex: /([\s\S]*?)<\/summary>/gi, processor: (m) => m[1] }, + { regex: /^##\s+Summary[^\n]*\n([\s\S]*?)(?=\n##\s+[^#]|\n🔧|$)/gm, processor: (m) => m[1] }, + { + regex: /^##\s+(Feature|Changes|Implementation)[^\n]*\n([\s\S]*?)(?=\n##\s+[^#]|\n🔧|$)/gm, + processor: (m) => `## ${m[1]}\n${m[2]}`, + }, + { + regex: /(^|\n)(All tasks completed[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/g, + processor: (m) => m[2], + }, + { + regex: + /(^|\n)((I've|I have) (successfully |now )?(completed|finished|implemented)[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/g, + processor: (m) => m[2], + }, + ]; - // Try other summary formats (Feature, Changes, Implementation) - const otherHeaderMatch = rawOutput.match( - /^##\s+(Feature|Changes|Implementation)\s*\n([\s\S]*?)(?=\n##\s+|$)/m - ); - if (otherHeaderMatch) { - return `## ${otherHeaderMatch[1]}\n${otherHeaderMatch[2].trim()}`; - } - - // Try to find summary introduction lines - const introMatch = rawOutput.match( - /(^|\n)(All tasks completed[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/ - ); - if (introMatch) { - return introMatch[2].trim(); - } - - const completionMatch = rawOutput.match( - /(^|\n)((I've|I have) (successfully |now )?(completed|finished|implemented)[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/ - ); - if (completionMatch) { - return completionMatch[2].trim(); + for (const { regex, processor } of regexesToTry) { + const matches = [...cleanedOutput.matchAll(regex)]; + if (matches.length > 0) { + const lastMatch = matches[matches.length - 1]; + return cleanFragmentedText(processor(lastMatch)).trim(); + } } return null;