Fix/small fixes 2.7.3 (#631)
* refactor(ui): move show archived button from project tab to Done column The "Show Archived" toggle button was located in the project tab header, which was not intuitive since archived tasks are related to the Done column. This moves the button to the Done column header where it makes more contextual sense. Changes: - Added toggle button to Done column in KanbanBoard.tsx - Button only appears when archived tasks exist (count > 0) - Hide "Archive All" button when viewing archived tasks to avoid confusion - Removed button and related props from SortableProjectTab, ProjectTabBar, App 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(terminal): fix terminal auto-naming in packaged release builds Terminal auto-naming was failing silently in release builds because getAutoBuildSourcePath() didn't check process.resourcesPath for packaged apps. Added app.isPackaged check to use bundled backend path first, with fallback to userData for user-updated backends. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(terminal): add SHIFT+Enter and CMD/Ctrl+Backspace keyboard shortcuts Add missing keyboard shortcuts to match VS Code/Cursor terminal behavior: - SHIFT+Enter: Insert newline for multi-line input in Claude Code CLI - CMD+Backspace (Mac) / Ctrl+Backspace (Windows/Linux): Delete line xterm.js doesn't natively support these shortcuts, so we intercept them in the custom key handler and send the appropriate escape sequences. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(ui): move show archived button from project tab to Done column The "Show Archived" toggle button was located in the project tab header, which was not intuitive since archived tasks are related to the Done column. This moves the button to the Done column header where it makes more contextual sense. Changes: - Added toggle button to Done column in KanbanBoard.tsx - Button only appears when archived tasks exist (count > 0) - Hide "Archive All" button when viewing archived tasks to avoid confusion - Removed button and related props from SortableProjectTab, ProjectTabBar, App 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): address PR review findings for consistency and clarity - Use shared getEffectiveSourcePath() in insights/config.ts for consistent userData fallback path detection (matches terminal-name-generator.ts) - Simplify redundant modifier key condition in useXterm.ts using existing isMod variable - Make archivedCount check explicit with !== undefined in KanbanBoard.tsx - Add 'common' namespace to useTranslation for proper cross-namespace access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): remove task card description truncation User requested full task descriptions on Kanban cards instead of 150-character previews. Removed character limit from sanitizeMarkdownForDisplay call and removed line-clamp-2 CSS class. Kanban columns already have ScrollArea so cards will scroll naturally if they get taller. * fix(ui): properly disable task card description truncation The previous change only removed the explicit 150 char limit, falling back to the default 200 char limit. This fix: - Updates sanitizeMarkdownForDisplay() to treat maxLength=0 as "no truncation" - Passes 0 from TaskCard to fully disable description truncation on cards * fix(main): consistent path resolution order in terminal-name-generator The path resolution order was inconsistent with path-resolver.ts: - terminal-name-generator checked bundled backend BEFORE userData override - path-resolver checks userData override FIRST (correct priority) This could cause version mismatches when users update their backend. Now both modules check userData override first, falling back to bundled. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,8 @@ export class AgentProcessManager {
|
||||
|
||||
// Auto-detect from app location (configured path was invalid or not set)
|
||||
const possiblePaths = [
|
||||
// Packaged app: backend is in extraResources (process.resourcesPath/backend)
|
||||
...(app.isPackaged ? [path.join(process.resourcesPath, 'backend')] : []),
|
||||
// Dev mode: from dist/main -> ../../backend (apps/frontend/out/main -> apps/backend)
|
||||
path.resolve(__dirname, '..', '..', '..', 'backend'),
|
||||
// Alternative: from app root -> apps/backend
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getProfileEnv } from '../rate-limit-detector';
|
||||
import { getValidatedPythonPath } from '../python-detector';
|
||||
import { getConfiguredPythonPath, pythonEnvManager } from '../python-env-manager';
|
||||
import { getAugmentedEnv } from '../env-utils';
|
||||
import { getEffectiveSourcePath } from '../updater/path-resolver';
|
||||
|
||||
/**
|
||||
* Configuration manager for insights service
|
||||
@@ -41,24 +42,23 @@ export class InsightsConfig {
|
||||
|
||||
/**
|
||||
* Get the auto-claude source path (detects automatically if not configured)
|
||||
* Uses getEffectiveSourcePath() which handles userData override for user-updated backend
|
||||
*/
|
||||
getAutoBuildSourcePath(): string | null {
|
||||
if (this.autoBuildSourcePath && existsSync(this.autoBuildSourcePath)) {
|
||||
return this.autoBuildSourcePath;
|
||||
}
|
||||
|
||||
const possiblePaths = [
|
||||
// Apps structure: from out/main -> apps/backend
|
||||
path.resolve(__dirname, '..', '..', '..', 'backend'),
|
||||
path.resolve(app.getAppPath(), '..', 'backend'),
|
||||
path.resolve(process.cwd(), 'apps', 'backend')
|
||||
];
|
||||
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(p) && existsSync(path.join(p, 'runners', 'spec_runner.py'))) {
|
||||
return p;
|
||||
}
|
||||
// Use shared path resolver which handles:
|
||||
// 1. User settings (autoBuildPath)
|
||||
// 2. userData override (backend-source) for user-updated backend
|
||||
// 3. Bundled backend (process.resourcesPath/backend)
|
||||
// 4. Development paths
|
||||
const effectivePath = getEffectiveSourcePath();
|
||||
if (existsSync(effectivePath) && existsSync(path.join(effectivePath, 'runners', 'spec_runner.py'))) {
|
||||
return effectivePath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,23 @@ export class TerminalNameGenerator extends EventEmitter {
|
||||
return this.autoBuildSourcePath;
|
||||
}
|
||||
|
||||
// In packaged app, check userData override first (consistent with path-resolver.ts)
|
||||
if (app.isPackaged) {
|
||||
// Check for user-updated backend source first (takes priority over bundled)
|
||||
const overridePath = path.join(app.getPath('userData'), 'backend-source');
|
||||
if (existsSync(overridePath) && existsSync(path.join(overridePath, 'runners', 'spec_runner.py'))) {
|
||||
debug('Using user-updated backend from userData:', overridePath);
|
||||
return overridePath;
|
||||
}
|
||||
// Fall back to bundled backend in resources
|
||||
const resourcesPath = path.join(process.resourcesPath, 'backend');
|
||||
if (existsSync(resourcesPath) && existsSync(path.join(resourcesPath, 'runners', 'spec_runner.py'))) {
|
||||
debug('Using bundled backend from resources:', resourcesPath);
|
||||
return resourcesPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Development mode paths
|
||||
const possiblePaths = [
|
||||
// Apps structure: from out/main -> apps/backend
|
||||
path.resolve(__dirname, '..', '..', '..', 'backend'),
|
||||
|
||||
@@ -63,10 +63,9 @@ 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, useViewState } from './contexts/ViewStateContext';
|
||||
import { ViewStateProvider } from './contexts/ViewStateContext';
|
||||
|
||||
// Wrapper component that connects ProjectTabBar to ViewStateContext
|
||||
// (needed because App renders the Provider and can't use useViewState directly)
|
||||
// Wrapper component for ProjectTabBar
|
||||
interface ProjectTabBarWithContextProps {
|
||||
projects: Project[];
|
||||
activeProjectId: string | null;
|
||||
@@ -74,7 +73,6 @@ interface ProjectTabBarWithContextProps {
|
||||
onProjectClose: (projectId: string) => void;
|
||||
onAddProject: () => void;
|
||||
onSettingsClick: () => void;
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
function ProjectTabBarWithContext({
|
||||
@@ -83,12 +81,8 @@ function ProjectTabBarWithContext({
|
||||
onProjectSelect,
|
||||
onProjectClose,
|
||||
onAddProject,
|
||||
onSettingsClick,
|
||||
tasks
|
||||
onSettingsClick
|
||||
}: ProjectTabBarWithContextProps) {
|
||||
const { showArchived, toggleShowArchived } = useViewState();
|
||||
const archivedCount = tasks.filter(t => t.metadata?.archivedAt).length;
|
||||
|
||||
return (
|
||||
<ProjectTabBar
|
||||
projects={projects}
|
||||
@@ -97,9 +91,6 @@ function ProjectTabBarWithContext({
|
||||
onProjectClose={onProjectClose}
|
||||
onAddProject={onAddProject}
|
||||
onSettingsClick={onSettingsClick}
|
||||
showArchived={showArchived}
|
||||
archivedCount={archivedCount}
|
||||
onToggleArchived={toggleShowArchived}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -721,7 +712,6 @@ export function App() {
|
||||
onProjectClose={handleProjectTabClose}
|
||||
onAddProject={handleAddProject}
|
||||
onSettingsClick={() => setIsSettingsDialogOpen(true)}
|
||||
tasks={tasks}
|
||||
/>
|
||||
</SortableContext>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw } from 'lucide-react';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
import { Button } from './ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import { TaskCard } from './TaskCard';
|
||||
import { SortableTaskCard } from './SortableTaskCard';
|
||||
import { TASK_STATUS_COLUMNS, TASK_STATUS_LABELS } from '../../shared/constants';
|
||||
@@ -44,6 +45,9 @@ interface DroppableColumnProps {
|
||||
isOver: boolean;
|
||||
onAddClick?: () => void;
|
||||
onArchiveAll?: () => void;
|
||||
archivedCount?: number;
|
||||
showArchived?: boolean;
|
||||
onToggleArchived?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +87,9 @@ function droppableColumnPropsAreEqual(
|
||||
if (prevProps.onTaskClick !== nextProps.onTaskClick) return false;
|
||||
if (prevProps.onAddClick !== nextProps.onAddClick) return false;
|
||||
if (prevProps.onArchiveAll !== nextProps.onArchiveAll) return false;
|
||||
if (prevProps.archivedCount !== nextProps.archivedCount) return false;
|
||||
if (prevProps.showArchived !== nextProps.showArchived) return false;
|
||||
if (prevProps.onToggleArchived !== nextProps.onToggleArchived) return false;
|
||||
|
||||
// Deep compare tasks
|
||||
const tasksEqual = tasksAreEquivalent(prevProps.tasks, nextProps.tasks);
|
||||
@@ -136,8 +143,8 @@ const getEmptyStateContent = (status: TaskStatus, t: (key: string) => string): {
|
||||
}
|
||||
};
|
||||
|
||||
const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArchiveAll }: DroppableColumnProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArchiveAll, archivedCount, showArchived, onToggleArchived }: DroppableColumnProps) {
|
||||
const { t } = useTranslation(['tasks', 'common']);
|
||||
const { setNodeRef } = useDroppable({
|
||||
id: status
|
||||
});
|
||||
@@ -216,7 +223,7 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{status === 'done' && onArchiveAll && tasks.length > 0 && (
|
||||
{status === 'done' && onArchiveAll && tasks.length > 0 && !showArchived && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -227,6 +234,32 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli
|
||||
<Archive className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{status === 'done' && archivedCount !== undefined && archivedCount > 0 && onToggleArchived && (
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'h-7 w-7 transition-colors relative',
|
||||
showArchived
|
||||
? 'text-primary bg-primary/10 hover:bg-primary/20'
|
||||
: 'hover:bg-muted-foreground/10 hover:text-muted-foreground'
|
||||
)}
|
||||
onClick={onToggleArchived}
|
||||
aria-pressed={showArchived}
|
||||
>
|
||||
<Archive className="h-4 w-4" />
|
||||
<span className="absolute -top-1 -right-1 text-[10px] font-medium bg-muted rounded-full min-w-[14px] h-[14px] flex items-center justify-center">
|
||||
{archivedCount}
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{showArchived ? t('common:projectTab.hideArchived') : t('common:projectTab.showArchived')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -281,7 +314,13 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
const { t } = useTranslation('tasks');
|
||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||
const [overColumnId, setOverColumnId] = useState<string | null>(null);
|
||||
const { showArchived } = useViewState();
|
||||
const { showArchived, toggleShowArchived } = useViewState();
|
||||
|
||||
// Calculate archived count for Done column button
|
||||
const archivedCount = useMemo(() =>
|
||||
tasks.filter(t => t.metadata?.archivedAt).length,
|
||||
[tasks]
|
||||
);
|
||||
|
||||
// Filter tasks based on archive status
|
||||
const filteredTasks = useMemo(() => {
|
||||
@@ -445,6 +484,9 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
isOver={overColumnId === status}
|
||||
onAddClick={status === 'backlog' ? onNewTaskClick : undefined}
|
||||
onArchiveAll={status === 'done' ? handleArchiveAll : undefined}
|
||||
archivedCount={status === 'done' ? archivedCount : undefined}
|
||||
showArchived={status === 'done' ? showArchived : undefined}
|
||||
onToggleArchived={status === 'done' ? toggleShowArchived : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -15,9 +15,6 @@ interface ProjectTabBarProps {
|
||||
className?: string;
|
||||
// Control props for active tab
|
||||
onSettingsClick?: () => void;
|
||||
showArchived?: boolean;
|
||||
archivedCount?: number;
|
||||
onToggleArchived?: () => void;
|
||||
}
|
||||
|
||||
export function ProjectTabBar({
|
||||
@@ -27,10 +24,7 @@ export function ProjectTabBar({
|
||||
onProjectClose,
|
||||
onAddProject,
|
||||
className,
|
||||
onSettingsClick,
|
||||
showArchived,
|
||||
archivedCount,
|
||||
onToggleArchived
|
||||
onSettingsClick
|
||||
}: ProjectTabBarProps) {
|
||||
// Keyboard shortcuts for tab navigation
|
||||
useEffect(() => {
|
||||
@@ -109,9 +103,6 @@ export function ProjectTabBar({
|
||||
}}
|
||||
// Pass control props only for active tab
|
||||
onSettingsClick={isActiveTab ? onSettingsClick : undefined}
|
||||
showArchived={isActiveTab ? showArchived : undefined}
|
||||
archivedCount={isActiveTab ? archivedCount : undefined}
|
||||
onToggleArchived={isActiveTab ? onToggleArchived : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Settings2, Archive } from 'lucide-react';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import type { Project } from '../../shared/types';
|
||||
@@ -15,9 +15,6 @@ interface SortableProjectTabProps {
|
||||
onClose: (e: React.MouseEvent) => void;
|
||||
// Optional control props for active tab
|
||||
onSettingsClick?: () => void;
|
||||
showArchived?: boolean;
|
||||
archivedCount?: number;
|
||||
onToggleArchived?: () => void;
|
||||
}
|
||||
|
||||
// Detect if running on macOS for keyboard shortcut display
|
||||
@@ -31,10 +28,7 @@ export function SortableProjectTab({
|
||||
tabIndex,
|
||||
onSelect,
|
||||
onClose,
|
||||
onSettingsClick,
|
||||
showArchived,
|
||||
archivedCount,
|
||||
onToggleArchived
|
||||
onSettingsClick
|
||||
}: SortableProjectTabProps) {
|
||||
const { t } = useTranslation('common');
|
||||
// Build tooltip with keyboard shortcut hint (only for tabs 1-9)
|
||||
@@ -148,42 +142,6 @@ export function SortableProjectTab({
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Archive toggle button with badge - responsive sizing */}
|
||||
{onToggleArchived && (
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'h-5 sm:h-6 px-1 sm:px-1.5 rounded',
|
||||
'flex items-center justify-center gap-0.5 sm:gap-1',
|
||||
'transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
|
||||
showArchived
|
||||
? 'text-primary bg-primary/10 hover:bg-primary/20'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleArchived();
|
||||
}}
|
||||
aria-label={showArchived ? t('projectTab.hideArchivedTasks') : t('projectTab.showArchivedTasks')}
|
||||
aria-pressed={showArchived}
|
||||
>
|
||||
<Archive className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||
{typeof archivedCount === 'number' && archivedCount > 0 && (
|
||||
<span className="text-[9px] sm:text-[10px] font-medium min-w-[12px] sm:min-w-[14px] text-center">
|
||||
{archivedCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<span>{showArchived ? t('projectTab.hideArchived') : t('projectTab.showArchived')}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -100,8 +100,9 @@ export const TaskCard = memo(function TaskCard({ task, onClick }: TaskCardProps)
|
||||
const isIncomplete = isIncompleteHumanReview(task);
|
||||
|
||||
// Memoize expensive computations to avoid running on every render
|
||||
// Pass 0 to disable truncation - show full description on cards
|
||||
const sanitizedDescription = useMemo(
|
||||
() => task.description ? sanitizeMarkdownForDisplay(task.description, 150) : null,
|
||||
() => task.description ? sanitizeMarkdownForDisplay(task.description, 0) : null,
|
||||
[task.description]
|
||||
);
|
||||
|
||||
@@ -278,7 +279,7 @@ export const TaskCard = memo(function TaskCard({ task, onClick }: TaskCardProps)
|
||||
|
||||
{/* Description - sanitized to handle markdown content (memoized) */}
|
||||
{sanitizedDescription && (
|
||||
<p className="mt-2 text-xs text-muted-foreground line-clamp-2">
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
{sanitizedDescription}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -73,6 +73,22 @@ export function useXterm({ terminalId, onCommandEnter, onResize }: UseXtermOptio
|
||||
xterm.attachCustomKeyEventHandler((event) => {
|
||||
const isMod = event.metaKey || event.ctrlKey;
|
||||
|
||||
// Handle SHIFT+Enter for multi-line input (send newline character)
|
||||
// This matches VS Code/Cursor behavior for multi-line input in Claude Code
|
||||
if (event.key === 'Enter' && event.shiftKey && !isMod && event.type === 'keydown') {
|
||||
// Send ESC + newline - same as OPTION+Enter which works for multi-line
|
||||
xterm.input('\x1b\n');
|
||||
return false; // Prevent default xterm handling
|
||||
}
|
||||
|
||||
// Handle CMD+Backspace (Mac) or Ctrl+Backspace (Windows/Linux) to delete line
|
||||
// Sends Ctrl+U which is the terminal standard for "kill line backward"
|
||||
const isDeleteLine = event.key === 'Backspace' && event.type === 'keydown' && isMod;
|
||||
if (isDeleteLine) {
|
||||
xterm.input('\x15'); // Ctrl+U
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let Cmd/Ctrl + number keys pass through for project tab switching
|
||||
if (isMod && event.key >= '1' && event.key <= '9') {
|
||||
return false; // Don't handle in xterm, let it bubble up
|
||||
|
||||
@@ -79,8 +79,8 @@ export function sanitizeMarkdownForDisplay(text: string, maxLength: number = 200
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Truncate if needed
|
||||
if (sanitized.length > maxLength) {
|
||||
// Truncate if needed (0 means no truncation)
|
||||
if (maxLength > 0 && sanitized.length > maxLength) {
|
||||
sanitized = sanitized.substring(0, maxLength).trim() + '...';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user