Files
Aperant/apps/frontend/src/renderer/lib/utils.ts
T
Andy 39da81935b 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>
2026-01-03 22:50:53 +01:00

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;
}