39da81935b
* 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>
89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
import { clsx, type ClassValue } from 'clsx';
|
|
import { twMerge } from 'tailwind-merge';
|
|
|
|
/**
|
|
* Utility function to merge Tailwind CSS classes
|
|
*/
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
/**
|
|
* Calculate progress percentage from subtasks
|
|
* @param subtasks Array of subtasks with status
|
|
* @returns Progress percentage (0-100)
|
|
*/
|
|
export function calculateProgress(subtasks: { status: string }[]): number {
|
|
if (subtasks.length === 0) return 0;
|
|
const completed = subtasks.filter((s) => s.status === 'completed').length;
|
|
return Math.round((completed / subtasks.length) * 100);
|
|
}
|
|
|
|
/**
|
|
* Format a date as a relative time string
|
|
* @param date Date to format
|
|
* @returns Relative time string (e.g., "2 hours ago")
|
|
*/
|
|
export function formatRelativeTime(date: Date): string {
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - new Date(date).getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMins / 60);
|
|
const diffDays = Math.floor(diffHours / 24);
|
|
|
|
if (diffMins < 1) return 'just now';
|
|
if (diffMins < 60) return `${diffMins}m ago`;
|
|
if (diffHours < 24) return `${diffHours}h ago`;
|
|
if (diffDays < 7) return `${diffDays}d ago`;
|
|
return new Date(date).toLocaleDateString();
|
|
}
|
|
|
|
/**
|
|
* Sanitize and extract plain text from markdown content.
|
|
* Strips markdown formatting and collapses whitespace for clean display in UI.
|
|
* @param text The text that might contain markdown
|
|
* @param maxLength Maximum length before truncation (default: 200)
|
|
* @returns Plain text suitable for display
|
|
*/
|
|
export function sanitizeMarkdownForDisplay(text: string, maxLength: number = 200): string {
|
|
if (!text) return '';
|
|
|
|
let sanitized = text
|
|
// Remove markdown headers (# ## ### etc)
|
|
.replace(/^#{1,6}\s+/gm, '')
|
|
// Remove bold/italic markers
|
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
.replace(/\*([^*]+)\*/g, '$1')
|
|
.replace(/__([^_]+)__/g, '$1')
|
|
.replace(/_([^_]+)_/g, '$1')
|
|
// Remove inline code
|
|
.replace(/`([^`]+)`/g, '$1')
|
|
// Remove code blocks
|
|
.replace(/```[\s\S]*?```/g, '')
|
|
// Remove links but keep text
|
|
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
// Remove images
|
|
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '')
|
|
// Remove horizontal rules
|
|
.replace(/^[-*_]{3,}$/gm, '')
|
|
// Remove blockquotes
|
|
.replace(/^>\s*/gm, '')
|
|
// Remove list markers
|
|
.replace(/^[\s]*[-*+]\s+/gm, '')
|
|
.replace(/^[\s]*\d+\.\s+/gm, '')
|
|
// Remove checkbox markers
|
|
.replace(/\[[ x]\]\s*/gi, '')
|
|
// Collapse multiple newlines to single space
|
|
.replace(/\n+/g, ' ')
|
|
// Collapse multiple spaces to single space
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
|
|
// Truncate if needed (0 means no truncation)
|
|
if (maxLength > 0 && sanitized.length > maxLength) {
|
|
sanitized = sanitized.substring(0, maxLength).trim() + '...';
|
|
}
|
|
|
|
return sanitized;
|
|
}
|