chore: reformat files after running npm run lint:fix

This commit is contained in:
Haileyesus
2026-03-05 17:30:21 +03:00
parent 28e9bf7eb0
commit 6e55bd2d75
162 changed files with 1685 additions and 1693 deletions

View File

@@ -1,15 +1,13 @@
import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Sidebar from '../sidebar/view/Sidebar';
import MainContent from '../main-content/view/MainContent';
import MobileNav from './MobileNav';
import { useWebSocket } from '../../contexts/WebSocketContext';
import { useDeviceSettings } from '../../hooks/useDeviceSettings';
import { useSessionProtection } from '../../hooks/useSessionProtection';
import { useProjectsState } from '../../hooks/useProjectsState';
import MobileNav from './MobileNav';
export default function AppContent() {
const navigate = useNavigate();
@@ -98,7 +96,7 @@ export default function AppContent() {
</div>
) : (
<div
className={`fixed inset-0 z-50 flex transition-all duration-150 ease-out ${sidebarOpen ? 'opacity-100 visible' : 'opacity-0 invisible'
className={`fixed inset-0 z-50 flex transition-all duration-150 ease-out ${sidebarOpen ? 'visible opacity-100' : 'invisible opacity-0'
}`}
>
<button
@@ -115,7 +113,7 @@ export default function AppContent() {
aria-label={t('versionUpdate.ariaLabels.closeSidebar')}
/>
<div
className={`relative w-[85vw] max-w-sm sm:w-80 h-full bg-card border-r border-border/40 transform transition-transform duration-150 ease-out ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'
className={`relative h-full w-[85vw] max-w-sm transform border-r border-border/40 bg-card transition-transform duration-150 ease-out sm:w-80 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
onClick={(event) => event.stopPropagation()}
onTouchStart={(event) => event.stopPropagation()}
@@ -125,7 +123,7 @@ export default function AppContent() {
</div>
)}
<div className={`flex-1 flex flex-col min-w-0 ${isMobile ? 'pb-mobile-nav' : ''}`}>
<div className={`flex min-w-0 flex-1 flex-col ${isMobile ? 'pb-mobile-nav' : ''}`}>
<MainContent
selectedProject={selectedProject}
selectedSession={selectedSession}

View File

@@ -1,6 +1,6 @@
import { MessageSquare, Folder, Terminal, GitBranch, ClipboardCheck } from 'lucide-react';
import { useTasksSettings } from '../../contexts/TasksSettingsContext';
import { Dispatch, SetStateAction } from 'react';
import { useTasksSettings } from '../../contexts/TasksSettingsContext';
import { AppTab } from '../../types/app';
type MobileNavProps = {
@@ -48,11 +48,11 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M
return (
<div
className={`fixed bottom-0 left-0 right-0 z-50 px-3 pb-[max(8px,env(safe-area-inset-bottom))] transform transition-transform duration-300 ease-in-out ${isInputFocused ? 'translate-y-full' : 'translate-y-0'
className={`fixed bottom-0 left-0 right-0 z-50 transform px-3 pb-[max(8px,env(safe-area-inset-bottom))] transition-transform duration-300 ease-in-out ${isInputFocused ? 'translate-y-full' : 'translate-y-0'
}`}
>
<div className="nav-glass mobile-nav-float rounded-2xl border border-border/30">
<div className="flex items-center justify-around px-1 py-1.5 gap-0.5">
<div className="flex items-center justify-around gap-0.5 px-1 py-1.5">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = activeTab === item.id;
@@ -65,7 +65,7 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M
e.preventDefault();
item.onClick();
}}
className={`flex flex-col items-center justify-center gap-0.5 px-3 py-2 rounded-xl flex-1 relative touch-manipulation transition-all duration-200 active:scale-95 ${isActive
className={`relative flex flex-1 touch-manipulation flex-col items-center justify-center gap-0.5 rounded-xl px-3 py-2 transition-all duration-200 active:scale-95 ${isActive
? 'text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
@@ -73,10 +73,10 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M
aria-current={isActive ? 'page' : undefined}
>
{isActive && (
<div className="absolute inset-0 bg-primary/8 dark:bg-primary/12 rounded-xl" />
<div className="bg-primary/8 dark:bg-primary/12 absolute inset-0 rounded-xl" />
)}
<Icon
className={`relative z-10 transition-all duration-200 ${isActive ? 'w-5 h-5' : 'w-[18px] h-[18px]'}`}
className={`relative z-10 transition-all duration-200 ${isActive ? 'h-5 w-5' : 'h-[18px] w-[18px]'}`}
strokeWidth={isActive ? 2.4 : 1.8}
/>
<span className={`relative z-10 text-[10px] font-medium transition-all duration-200 ${isActive ? 'opacity-100' : 'opacity-60'}`}>

View File

@@ -8,7 +8,7 @@ export default function AuthErrorAlert({ errorMessage }: AuthErrorAlertProps) {
}
return (
<div className="p-3 bg-red-100 dark:bg-red-900/20 border border-red-300 dark:border-red-800 rounded-md">
<div className="rounded-md border border-red-300 bg-red-100 p-3 dark:border-red-800 dark:bg-red-900/20">
<p className="text-sm text-red-700 dark:text-red-400">{errorMessage}</p>
</div>
);

View File

@@ -19,7 +19,7 @@ export default function AuthInputField({
}: AuthInputFieldProps) {
return (
<div>
<label htmlFor={id} className="block text-sm font-medium text-foreground mb-1">
<label htmlFor={id} className="mb-1 block text-sm font-medium text-foreground">
{label}
</label>
<input
@@ -27,7 +27,7 @@ export default function AuthInputField({
type={type}
value={value}
onChange={(event) => onChange(event.target.value)}
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
className="w-full rounded-md border border-border bg-background px-3 py-2 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={placeholder}
required
disabled={isDisabled}

View File

@@ -4,27 +4,27 @@ const loadingDotAnimationDelays = ['0s', '0.1s', '0.2s'];
export default function AuthLoadingScreen() {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center">
<div className="flex justify-center mb-4">
<div className="w-16 h-16 bg-primary rounded-lg flex items-center justify-center shadow-sm">
<MessageSquare className="w-8 h-8 text-primary-foreground" />
<div className="mb-4 flex justify-center">
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-primary shadow-sm">
<MessageSquare className="h-8 w-8 text-primary-foreground" />
</div>
</div>
<h1 className="text-2xl font-bold text-foreground mb-2">Claude Code UI</h1>
<h1 className="mb-2 text-2xl font-bold text-foreground">Claude Code UI</h1>
<div className="flex items-center justify-center space-x-2">
{loadingDotAnimationDelays.map((delay) => (
<div
key={delay}
className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
className="h-2 w-2 animate-bounce rounded-full bg-blue-500"
style={{ animationDelay: delay }}
/>
))}
</div>
<p className="text-muted-foreground mt-2">Loading...</p>
<p className="mt-2 text-muted-foreground">Loading...</p>
</div>
</div>
);

View File

@@ -17,19 +17,19 @@ export default function AuthScreenLayout({
logo,
}: AuthScreenLayoutProps) {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="w-full max-w-md">
<div className="bg-card rounded-lg shadow-lg border border-border p-8 space-y-6">
<div className="space-y-6 rounded-lg border border-border bg-card p-8 shadow-lg">
<div className="text-center">
<div className="flex justify-center mb-4">
<div className="mb-4 flex justify-center">
{logo ?? (
<div className="w-16 h-16 bg-primary rounded-lg flex items-center justify-center shadow-sm">
<MessageSquare className="w-8 h-8 text-primary-foreground" />
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-primary shadow-sm">
<MessageSquare className="h-8 w-8 text-primary-foreground" />
</div>
)}
</div>
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
<p className="text-muted-foreground mt-2">{description}</p>
<p className="mt-2 text-muted-foreground">{description}</p>
</div>
{children}

View File

@@ -80,7 +80,7 @@ export default function LoginForm() {
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium py-2 px-4 rounded-md transition-colors duration-200"
className="w-full rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:bg-blue-400"
>
{isSubmitting ? t('login.loading') : t('login.submit')}
</button>

View File

@@ -1,9 +1,9 @@
import type { ReactNode } from 'react';
import { IS_PLATFORM } from '../../../constants/config';
import { useAuth } from '../context/AuthContext';
import Onboarding from '../../onboarding/view/Onboarding';
import AuthLoadingScreen from './AuthLoadingScreen';
import LoginForm from './LoginForm';
import Onboarding from '../../onboarding/view/Onboarding';
import SetupForm from './SetupForm';
type ProtectedRouteProps = {

View File

@@ -74,7 +74,7 @@ export default function SetupForm() {
title="Welcome to Claude Code UI"
description="Set up your account to get started"
footerText="This is a single-user system. Only one account can be created."
logo={<img src="/logo.svg" alt="CloudCLI" className="w-16 h-16" />}
logo={<img src="/logo.svg" alt="CloudCLI" className="h-16 w-16" />}
>
<form onSubmit={handleSubmit} className="space-y-4">
<AuthInputField
@@ -111,7 +111,7 @@ export default function SetupForm() {
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium py-2 px-4 rounded-md transition-colors duration-200"
className="w-full rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:bg-blue-400"
>
{isSubmitting ? 'Setting up...' : 'Create Account'}
</button>

View File

@@ -11,9 +11,7 @@ import type {
} from 'react';
import { useDropzone } from 'react-dropzone';
import { authenticatedFetch } from '../../../utils/api';
import { thinkingModes } from '../constants/thinkingModes';
import { grantClaudeToolPermission } from '../utils/chatPermissions';
import { safeLocalStorage } from '../utils/chatStorage';
import type {
@@ -21,10 +19,10 @@ import type {
PendingPermissionRequest,
PermissionMode,
} from '../types/types';
import { useFileMentions } from './useFileMentions';
import { type SlashCommand, useSlashCommands } from './useSlashCommands';
import type { Project, ProjectSession, SessionProvider } from '../../../types/app';
import { escapeRegExp } from '../utils/chatFormatting';
import { useFileMentions } from './useFileMentions';
import { type SlashCommand, useSlashCommands } from './useSlashCommands';
type PendingViewSession = {
sessionId: string | null;

View File

@@ -1,6 +1,5 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { MutableRefObject } from 'react';
import { api, authenticatedFetch } from '../../../utils/api';
import type { ChatMessage, Provider } from '../types/types';
import type { Project, ProjectSession } from '../../../types/app';

View File

@@ -161,7 +161,7 @@ export function useFileMentions({ selectedProject, input, setInput, textareaRef
fileMentionSet.has(part) ? (
<span
key={`mention-${index}`}
className="bg-blue-200/70 -ml-0.5 dark:bg-blue-300/40 px-0.5 rounded-md box-decoration-clone text-transparent"
className="-ml-0.5 rounded-md bg-blue-200/70 box-decoration-clone px-0.5 text-transparent dark:bg-blue-300/40"
>
{part}
</span>

View File

@@ -1,8 +1,8 @@
import React, { memo, useMemo, useCallback } from 'react';
import { getToolConfig } from './configs/toolConfigs';
import { OneLineDisplay, CollapsibleDisplay, ToolDiffViewer, MarkdownContent, FileListContent, TodoListContent, TaskListContent, TextContent, QuestionAnswerContent, SubagentContainer } from './components';
import type { Project } from '../../../types/app';
import type { SubagentChildTool } from '../types/types';
import { getToolConfig } from './configs/toolConfigs';
import { OneLineDisplay, CollapsibleDisplay, ToolDiffViewer, MarkdownContent, FileListContent, TodoListContent, TaskListContent, TextContent, QuestionAnswerContent, SubagentContainer } from './components';
type DiffLine = {
type: string;
@@ -202,7 +202,7 @@ export const ToolRenderer: React.FC<ToolRendererProps> = memo(({
const msg = displayConfig.getMessage?.(parsedData) || 'Success';
contentComponent = (
<div className="flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
{msg}

View File

@@ -43,7 +43,7 @@ export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
const borderColor = borderColorMap[toolCategory || 'default'] || borderColorMap.default;
return (
<div className={`border-l-2 ${borderColor} pl-3 py-0.5 my-1 ${className}`}>
<div className={`border-l-2 ${borderColor} my-1 py-0.5 pl-3 ${className}`}>
<CollapsibleSection
title={title}
toolName={toolName}
@@ -54,10 +54,10 @@ export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
{children}
{showRawParameters && rawContent && (
<details className="relative mt-2 group/raw">
<summary className="flex items-center gap-1.5 text-[11px] text-gray-400 dark:text-gray-500 cursor-pointer hover:text-gray-600 dark:hover:text-gray-300 py-0.5">
<details className="group/raw relative mt-2">
<summary className="flex cursor-pointer items-center gap-1.5 py-0.5 text-[11px] text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300">
<svg
className="w-2.5 h-2.5 transition-transform duration-150 group-open/raw:rotate-90"
className="h-2.5 w-2.5 transition-transform duration-150 group-open/raw:rotate-90"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -66,7 +66,7 @@ export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
</svg>
raw params
</summary>
<pre className="mt-1 text-[11px] bg-gray-50 dark:bg-gray-900/50 border border-gray-200/40 dark:border-gray-700/40 p-2 rounded whitespace-pre-wrap break-words overflow-hidden text-gray-600 dark:text-gray-400 font-mono">
<pre className="mt-1 overflow-hidden whitespace-pre-wrap break-words rounded border border-gray-200/40 bg-gray-50 p-2 font-mono text-[11px] text-gray-600 dark:border-gray-700/40 dark:bg-gray-900/50 dark:text-gray-400">
{rawContent}
</pre>
</details>

View File

@@ -23,10 +23,10 @@ export const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
className = ''
}) => {
return (
<details className={`relative group/details ${className}`} open={open}>
<summary className="flex items-center gap-1.5 text-xs cursor-pointer py-0.5 select-none group-open/details:sticky group-open/details:top-0 group-open/details:z-10 group-open/details:bg-background group-open/details:-mx-1 group-open/details:px-1">
<details className={`group/details relative ${className}`} open={open}>
<summary className="flex cursor-pointer select-none items-center gap-1.5 py-0.5 text-xs group-open/details:sticky group-open/details:top-0 group-open/details:z-10 group-open/details:-mx-1 group-open/details:bg-background group-open/details:px-1">
<svg
className="w-3 h-3 text-gray-400 dark:text-gray-500 transition-transform duration-150 group-open/details:rotate-90 flex-shrink-0"
className="h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-150 group-open/details:rotate-90 dark:text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -34,24 +34,24 @@ export const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
{toolName && (
<span className="font-medium text-gray-500 dark:text-gray-400 flex-shrink-0">{toolName}</span>
<span className="flex-shrink-0 font-medium text-gray-500 dark:text-gray-400">{toolName}</span>
)}
{toolName && (
<span className="text-gray-300 dark:text-gray-600 text-[10px] flex-shrink-0">/</span>
<span className="flex-shrink-0 text-[10px] text-gray-300 dark:text-gray-600">/</span>
)}
{onTitleClick ? (
<button
onClick={(e) => { e.preventDefault(); e.stopPropagation(); onTitleClick(); }}
className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-mono hover:underline truncate flex-1 text-left transition-colors"
className="flex-1 truncate text-left font-mono text-blue-600 transition-colors hover:text-blue-700 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
>
{title}
</button>
) : (
<span className="text-gray-600 dark:text-gray-400 truncate flex-1">
<span className="flex-1 truncate text-gray-600 dark:text-gray-400">
{title}
</span>
)}
{action && <span className="flex-shrink-0 ml-1">{action}</span>}
{action && <span className="ml-1 flex-shrink-0">{action}</span>}
</summary>
<div className="mt-1.5 pl-[18px]">
{children}

View File

@@ -23,11 +23,11 @@ export const FileListContent: React.FC<FileListContentProps> = ({
return (
<div>
{title && (
<div className="text-[11px] text-gray-500 dark:text-gray-400 mb-1">
<div className="mb-1 text-[11px] text-gray-500 dark:text-gray-400">
{title}
</div>
)}
<div className="flex flex-wrap gap-x-1 gap-y-0.5 max-h-48 overflow-y-auto">
<div className="flex max-h-48 flex-wrap gap-x-1 gap-y-0.5 overflow-y-auto">
{files.map((file, index) => {
const filePath = typeof file === 'string' ? file : file.path;
const fileName = filePath.split('/').pop() || filePath;
@@ -39,13 +39,13 @@ export const FileListContent: React.FC<FileListContentProps> = ({
<span key={index} className="inline-flex items-center">
<button
onClick={handleClick}
className="text-[11px] font-mono text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline transition-colors"
className="font-mono text-[11px] text-blue-600 transition-colors hover:text-blue-700 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
title={filePath}
>
{fileName}
</button>
{index < files.length - 1 && (
<span className="text-gray-300 dark:text-gray-600 text-[10px] ml-1">,</span>
<span className="ml-1 text-[10px] text-gray-300 dark:text-gray-600">,</span>
)}
</span>
);

View File

@@ -33,31 +33,31 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
return (
<div
key={idx}
className="rounded-lg border border-gray-150 dark:border-gray-700/50 bg-gray-50/50 dark:bg-gray-800/30 overflow-hidden"
className="border-gray-150 overflow-hidden rounded-lg border bg-gray-50/50 dark:border-gray-700/50 dark:bg-gray-800/30"
>
<button
type="button"
onClick={() => setExpandedIdx(isExpanded ? null : idx)}
className="w-full text-left px-3 py-2 flex items-start gap-2.5 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
className="flex w-full items-start gap-2.5 px-3 py-2 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50"
>
<div className={`mt-0.5 flex-shrink-0 w-4 h-4 rounded-full flex items-center justify-center ${
<div className={`mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full ${
answerLabels.length > 0
? 'bg-blue-100 dark:bg-blue-900/40'
: 'bg-gray-100 dark:bg-gray-800'
}`}>
{answerLabels.length > 0 ? (
<svg className="w-2.5 h-2.5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<svg className="h-2.5 w-2.5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
) : (
<div className="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600" />
<div className="h-1.5 w-1.5 rounded-full bg-gray-300 dark:bg-gray-600" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
{q.header && (
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-semibold uppercase tracking-wider bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 border border-blue-100/80 dark:border-blue-800/40">
<span className="inline-flex items-center rounded border border-blue-100/80 bg-blue-50 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-blue-600 dark:border-blue-800/40 dark:bg-blue-900/30 dark:text-blue-400">
{q.header}
</span>
)}
@@ -67,22 +67,22 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
</span>
)}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-0.5 leading-snug">
<div className="mt-0.5 text-xs leading-snug text-gray-600 dark:text-gray-400">
{q.question}
</div>
{!isExpanded && answerLabels.length > 0 && (
<div className="flex flex-wrap gap-1 mt-1.5">
<div className="mt-1.5 flex flex-wrap gap-1">
{answerLabels.map((lbl) => {
const isCustom = !q.options.some(o => o.label === lbl);
return (
<span
key={lbl}
className="inline-flex items-center gap-1 text-[11px] px-1.5 py-0.5 rounded-md bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium"
className="inline-flex items-center gap-1 rounded-md bg-blue-50 px-1.5 py-0.5 text-[11px] font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
>
{lbl}
{isCustom && (
<span className="text-[9px] text-blue-400 dark:text-blue-500 font-normal">(custom)</span>
<span className="text-[9px] font-normal text-blue-400 dark:text-blue-500">(custom)</span>
)}
</span>
);
@@ -91,14 +91,14 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
)}
{!isExpanded && skipped && hasAnyAnswer && (
<span className="inline-block mt-1 text-[10px] text-gray-400 dark:text-gray-500 italic">
<span className="mt-1 inline-block text-[10px] italic text-gray-400 dark:text-gray-500">
Skipped
</span>
)}
</div>
<svg
className={`w-3.5 h-3.5 mt-0.5 text-gray-400 dark:text-gray-500 flex-shrink-0 transition-transform duration-200 ${
className={`mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-gray-400 transition-transform duration-200 dark:text-gray-500 ${
isExpanded ? 'rotate-180' : ''
}`}
fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}
@@ -108,36 +108,36 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
</button>
{isExpanded && (
<div className="px-3 pb-2.5 pt-0.5 border-t border-gray-100 dark:border-gray-700/40">
<div className="space-y-1 ml-6.5">
<div className="border-t border-gray-100 px-3 pb-2.5 pt-0.5 dark:border-gray-700/40">
<div className="ml-6.5 space-y-1">
{q.options.map((opt) => {
const wasSelected = answerLabels.includes(opt.label);
return (
<div
key={opt.label}
className={`flex items-start gap-2 px-2.5 py-1.5 rounded-lg text-[12px] ${
className={`flex items-start gap-2 rounded-lg px-2.5 py-1.5 text-[12px] ${
wasSelected
? 'bg-blue-50/80 dark:bg-blue-900/20 border border-blue-200/60 dark:border-blue-800/40'
? 'border border-blue-200/60 bg-blue-50/80 dark:border-blue-800/40 dark:bg-blue-900/20'
: 'text-gray-400 dark:text-gray-500'
}`}
>
<div className={`mt-0.5 flex-shrink-0 w-3.5 h-3.5 ${q.multiSelect ? 'rounded-[3px]' : 'rounded-full'} border-[1.5px] flex items-center justify-center ${
<div className={`mt-0.5 h-3.5 w-3.5 flex-shrink-0 ${q.multiSelect ? 'rounded-[3px]' : 'rounded-full'} flex items-center justify-center border-[1.5px] ${
wasSelected
? 'border-blue-500 dark:border-blue-400 bg-blue-500 dark:bg-blue-500'
? 'border-blue-500 bg-blue-500 dark:border-blue-400 dark:bg-blue-500'
: 'border-gray-300 dark:border-gray-600'
}`}>
{wasSelected && (
<svg className="w-2 h-2 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<svg className="h-2 w-2 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)}
</div>
<div className="flex-1 min-w-0">
<span className={wasSelected ? 'text-gray-900 dark:text-gray-100 font-medium' : ''}>
<div className="min-w-0 flex-1">
<span className={wasSelected ? 'font-medium text-gray-900 dark:text-gray-100' : ''}>
{opt.label}
</span>
{opt.description && (
<span className={`block text-[11px] mt-0.5 ${
<span className={`mt-0.5 block text-[11px] ${
wasSelected ? 'text-blue-600/70 dark:text-blue-300/70' : 'text-gray-400 dark:text-gray-600'
}`}>
{opt.description}
@@ -151,22 +151,22 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
{answerLabels.filter(lbl => !q.options.some(o => o.label === lbl)).map(lbl => (
<div
key={lbl}
className="flex items-start gap-2 px-2.5 py-1.5 rounded-lg text-[12px] bg-blue-50/80 dark:bg-blue-900/20 border border-blue-200/60 dark:border-blue-800/40"
className="flex items-start gap-2 rounded-lg border border-blue-200/60 bg-blue-50/80 px-2.5 py-1.5 text-[12px] dark:border-blue-800/40 dark:bg-blue-900/20"
>
<div className={`mt-0.5 flex-shrink-0 w-3.5 h-3.5 ${q.multiSelect ? 'rounded-[3px]' : 'rounded-full'} border-[1.5px] border-blue-500 dark:border-blue-400 bg-blue-500 dark:bg-blue-500 flex items-center justify-center`}>
<svg className="w-2 h-2 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<div className={`mt-0.5 h-3.5 w-3.5 flex-shrink-0 ${q.multiSelect ? 'rounded-[3px]' : 'rounded-full'} flex items-center justify-center border-[1.5px] border-blue-500 bg-blue-500 dark:border-blue-400 dark:bg-blue-500`}>
<svg className="h-2 w-2 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<div className="flex-1 min-w-0">
<span className="text-gray-900 dark:text-gray-100 font-medium">{lbl}</span>
<span className="text-[10px] text-blue-500 dark:text-blue-400 ml-1">(custom)</span>
<div className="min-w-0 flex-1">
<span className="font-medium text-gray-900 dark:text-gray-100">{lbl}</span>
<span className="ml-1 text-[10px] text-blue-500 dark:text-blue-400">(custom)</span>
</div>
</div>
))}
{skipped && hasAnyAnswer && (
<div className="text-[11px] text-gray-400 dark:text-gray-500 italic px-2.5 py-1">
<div className="px-2.5 py-1 text-[11px] italic text-gray-400 dark:text-gray-500">
No answer provided
</div>
)}
@@ -178,7 +178,7 @@ export const QuestionAnswerContent: React.FC<QuestionAnswerContentProps> = ({
})}
{!hasAnyAnswer && total === 1 && (
<div className="text-[11px] text-gray-400 dark:text-gray-500 italic">
<div className="text-[11px] italic text-gray-400 dark:text-gray-500">
Skipped
</div>
)}

View File

@@ -39,7 +39,7 @@ function parseTaskContent(content: string): TaskItem[] {
const statusConfig = {
completed: {
icon: (
<svg className="w-3.5 h-3.5 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3.5 w-3.5 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
@@ -48,7 +48,7 @@ const statusConfig = {
},
in_progress: {
icon: (
<svg className="w-3.5 h-3.5 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3.5 w-3.5 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
@@ -57,7 +57,7 @@ const statusConfig = {
},
pending: {
icon: (
<svg className="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" strokeWidth={2} />
</svg>
),
@@ -76,7 +76,7 @@ export const TaskListContent: React.FC<TaskListContentProps> = ({ content }) =>
// If we couldn't parse any tasks, fall back to text display
if (tasks.length === 0) {
return (
<pre className="text-[11px] font-mono text-gray-600 dark:text-gray-400 whitespace-pre-wrap">
<pre className="whitespace-pre-wrap font-mono text-[11px] text-gray-600 dark:text-gray-400">
{content}
</pre>
);
@@ -87,13 +87,13 @@ export const TaskListContent: React.FC<TaskListContentProps> = ({ content }) =>
return (
<div>
<div className="flex items-center gap-2 mb-1.5">
<div className="mb-1.5 flex items-center gap-2">
<span className="text-[11px] text-gray-500 dark:text-gray-400">
{completed}/{total} completed
</span>
<div className="flex-1 h-1 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div className="h-1 flex-1 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div
className="h-full bg-green-500 dark:bg-green-400 rounded-full transition-all"
className="h-full rounded-full bg-green-500 transition-all dark:bg-green-400"
style={{ width: `${total > 0 ? (completed / total) * 100 : 0}%` }}
/>
</div>
@@ -104,16 +104,16 @@ export const TaskListContent: React.FC<TaskListContentProps> = ({ content }) =>
return (
<div
key={task.id}
className="flex items-center gap-1.5 py-0.5 group"
className="group flex items-center gap-1.5 py-0.5"
>
<span className="flex-shrink-0">{config.icon}</span>
<span className="text-[11px] font-mono text-gray-400 dark:text-gray-500 flex-shrink-0">
<span className="flex-shrink-0 font-mono text-[11px] text-gray-400 dark:text-gray-500">
#{task.id}
</span>
<span className={`text-xs truncate flex-1 ${config.textClass}`}>
<span className={`flex-1 truncate text-xs ${config.textClass}`}>
{task.subject}
</span>
<span className={`text-[10px] px-1 py-px rounded border flex-shrink-0 ${config.badgeClass}`}>
<span className={`flex-shrink-0 rounded border px-1 py-px text-[10px] ${config.badgeClass}`}>
{task.status.replace('_', ' ')}
</span>
</div>

View File

@@ -26,7 +26,7 @@ export const TextContent: React.FC<TextContentProps> = ({
}
return (
<pre className={`mt-1 text-xs bg-gray-900 dark:bg-gray-950 text-gray-100 p-2.5 rounded overflow-x-auto font-mono ${className}`}>
<pre className={`mt-1 overflow-x-auto rounded bg-gray-900 p-2.5 font-mono text-xs text-gray-100 dark:bg-gray-950 ${className}`}>
{formattedJson}
</pre>
);
@@ -34,7 +34,7 @@ export const TextContent: React.FC<TextContentProps> = ({
if (format === 'code') {
return (
<pre className={`mt-1 text-xs bg-gray-50 dark:bg-gray-800/50 border border-gray-200/50 dark:border-gray-700/50 p-2 rounded whitespace-pre-wrap break-words overflow-hidden text-gray-700 dark:text-gray-300 font-mono ${className}`}>
<pre className={`mt-1 overflow-hidden whitespace-pre-wrap break-words rounded border border-gray-200/50 bg-gray-50 p-2 font-mono text-xs text-gray-700 dark:border-gray-700/50 dark:bg-gray-800/50 dark:text-gray-300 ${className}`}>
{content}
</pre>
);
@@ -42,7 +42,7 @@ export const TextContent: React.FC<TextContentProps> = ({
// Plain text
return (
<div className={`mt-1 text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap ${className}`}>
<div className={`mt-1 whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-300 ${className}`}>
{content}
</div>
);

View File

@@ -79,25 +79,25 @@ const TodoRow = memo(
const StatusIcon = statusConfig.icon;
return (
<div className="flex items-start gap-2 p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded transition-colors">
<div className="flex-shrink-0 mt-0.5">
<div className="flex items-start gap-2 rounded border border-gray-200 bg-white p-2 transition-colors dark:border-gray-700 dark:bg-gray-800">
<div className="mt-0.5 flex-shrink-0">
<StatusIcon className={statusConfig.iconClassName} />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2 mb-0.5">
<div className="min-w-0 flex-1">
<div className="mb-0.5 flex items-start justify-between gap-2">
<p className={`text-xs font-medium ${statusConfig.textClassName}`}>
{todo.content}
</p>
<div className="flex gap-1 flex-shrink-0">
<div className="flex flex-shrink-0 gap-1">
<Badge
variant="outline"
className={`text-[10px] px-1.5 py-px ${PRIORITY_BADGE_CLASS[todo.priority]}`}
className={`px-1.5 py-px text-[10px] ${PRIORITY_BADGE_CLASS[todo.priority]}`}
>
{todo.priority}
</Badge>
<Badge
variant="outline"
className={`text-[10px] px-1.5 py-px ${statusConfig.badgeClassName}`}
className={`px-1.5 py-px text-[10px] ${statusConfig.badgeClassName}`}
>
{todo.status.replace('_', ' ')}
</Badge>
@@ -136,7 +136,7 @@ const TodoList = memo(
return (
<div className="space-y-1.5">
{isResult && (
<div className="text-xs font-medium text-gray-600 dark:text-gray-400 mb-1.5">
<div className="mb-1.5 text-xs font-medium text-gray-600 dark:text-gray-400">
Todo List ({normalizedTodos.length}{' '}
{normalizedTodos.length === 1 ? 'item' : 'items'})
</div>

View File

@@ -148,32 +148,32 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
tabIndex={-1}
onKeyDown={handleKeyDown}
className={`w-full outline-none transition-all duration-500 ease-out ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-3'
mounted ? 'translate-y-0 opacity-100' : 'translate-y-3 opacity-0'
}`}
>
<div className="relative overflow-hidden rounded-2xl border border-gray-200/80 dark:border-gray-700/50 bg-white dark:bg-gray-800/90 shadow-lg dark:shadow-2xl">
<div className="relative overflow-hidden rounded-2xl border border-gray-200/80 bg-white shadow-lg dark:border-gray-700/50 dark:bg-gray-800/90 dark:shadow-2xl">
{/* Accent line */}
<div className="absolute top-0 left-0 right-0 h-[2px] bg-gradient-to-r from-blue-500 via-cyan-400 to-teal-400" />
<div className="absolute left-0 right-0 top-0 h-[2px] bg-gradient-to-r from-blue-500 via-cyan-400 to-teal-400" />
{/* Header + Question — compact */}
<div className="px-4 pt-3.5 pb-2">
<div className="flex items-center gap-2.5 mb-1.5">
<div className="px-4 pb-2 pt-3.5">
<div className="mb-1.5 flex items-center gap-2.5">
{/* Question icon */}
<div className="relative flex-shrink-0">
<div className="w-6 h-6 rounded-lg bg-gradient-to-br from-blue-500/10 to-cyan-500/10 dark:from-blue-400/15 dark:to-cyan-400/15 flex items-center justify-center">
<svg className="w-3.5 h-3.5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.75} stroke="currentColor">
<div className="flex h-6 w-6 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500/10 to-cyan-500/10 dark:from-blue-400/15 dark:to-cyan-400/15">
<svg className="h-3.5 w-3.5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.75} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827m0 3h.01" />
</svg>
</div>
<div className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-cyan-400 dark:bg-cyan-500 animate-pulse" />
<div className="absolute -right-0.5 -top-0.5 h-2 w-2 animate-pulse rounded-full bg-cyan-400 dark:bg-cyan-500" />
</div>
<div className="flex items-center gap-2 min-w-0 flex-1">
<span className="text-[10px] font-medium tracking-wide uppercase text-gray-400 dark:text-gray-500">
<div className="flex min-w-0 flex-1 items-center gap-2">
<span className="text-[10px] font-medium uppercase tracking-wide text-gray-400 dark:text-gray-500">
Claude needs your input
</span>
{q.header && (
<span className="inline-flex items-center px-1.5 py-px rounded text-[9px] font-semibold uppercase tracking-wider bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 border border-blue-100 dark:border-blue-800/50">
<span className="inline-flex items-center rounded border border-blue-100 bg-blue-50 px-1.5 py-px text-[9px] font-semibold uppercase tracking-wider text-blue-600 dark:border-blue-800/50 dark:bg-blue-900/30 dark:text-blue-400">
{q.header}
</span>
)}
@@ -181,7 +181,7 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
{/* Step counter */}
{!isSingle && (
<span className="text-[10px] tabular-nums text-gray-400 dark:text-gray-500 flex-shrink-0">
<span className="flex-shrink-0 text-[10px] tabular-nums text-gray-400 dark:text-gray-500">
{currentStep + 1}/{total}
</span>
)}
@@ -189,7 +189,7 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
{/* Progress dots (multi-question) */}
{!isSingle && (
<div className="flex items-center gap-1 mb-2">
<div className="mb-2 flex items-center gap-1">
{questions.map((_, i) => (
<button
key={i}
@@ -208,7 +208,7 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
)}
{/* Question text */}
<p className="text-[14px] leading-snug font-medium text-gray-900 dark:text-gray-100">
<p className="text-[14px] font-medium leading-snug text-gray-900 dark:text-gray-100">
{q.question}
</p>
{multi && (
@@ -217,7 +217,7 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
</div>
{/* Options — tight spacing */}
<div className="px-4 pb-2 max-h-48 overflow-y-auto scrollbar-thin" role={multi ? 'group' : 'radiogroup'} aria-label={q.question}>
<div className="scrollbar-thin max-h-48 overflow-y-auto px-4 pb-2" role={multi ? 'group' : 'radiogroup'} aria-label={q.question}>
<div className="space-y-1">
{q.options.map((opt, optIdx) => {
const isSelected = selected.has(opt.label);
@@ -226,25 +226,25 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
key={opt.label}
type="button"
onClick={() => toggleOption(currentStep, opt.label, multi)}
className={`group w-full text-left flex items-center gap-2.5 px-3 py-2 rounded-lg border transition-all duration-150 ${
className={`group flex w-full items-center gap-2.5 rounded-lg border px-3 py-2 text-left transition-all duration-150 ${
isSelected
? 'border-blue-300 dark:border-blue-600 bg-blue-50/80 dark:bg-blue-900/25 ring-1 ring-blue-200/50 dark:ring-blue-700/30'
: 'border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50/60 dark:hover:bg-gray-750/50'
? 'border-blue-300 bg-blue-50/80 ring-1 ring-blue-200/50 dark:border-blue-600 dark:bg-blue-900/25 dark:ring-blue-700/30'
: 'dark:hover:bg-gray-750/50 border-gray-200 hover:border-gray-300 hover:bg-gray-50/60 dark:border-gray-700/60 dark:hover:border-gray-600'
}`}
>
{/* Keyboard hint */}
<kbd className={`flex-shrink-0 w-5 h-5 rounded text-[10px] font-mono flex items-center justify-center transition-all duration-150 ${
<kbd className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded font-mono text-[10px] transition-all duration-150 ${
isSelected
? 'bg-blue-500 dark:bg-blue-500 text-white font-semibold'
: 'bg-gray-100 dark:bg-gray-800 text-gray-400 dark:text-gray-500 border border-gray-200 dark:border-gray-700 group-hover:border-gray-300 dark:group-hover:border-gray-600'
? 'bg-blue-500 font-semibold text-white dark:bg-blue-500'
: 'border border-gray-200 bg-gray-100 text-gray-400 group-hover:border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500 dark:group-hover:border-gray-600'
}`}>
{optIdx + 1}
</kbd>
<div className="flex-1 min-w-0">
<div className="min-w-0 flex-1">
<div className={`text-[13px] leading-tight transition-colors duration-150 ${
isSelected
? 'text-gray-900 dark:text-gray-100 font-medium'
? 'font-medium text-gray-900 dark:text-gray-100'
: 'text-gray-700 dark:text-gray-300'
}`}>
{opt.label}
@@ -262,7 +262,7 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
{/* Selection check */}
{isSelected && (
<svg className="w-4 h-4 text-blue-500 dark:text-blue-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<svg className="h-4 w-4 flex-shrink-0 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
)}
@@ -274,28 +274,28 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
<button
type="button"
onClick={() => toggleOther(currentStep, multi)}
className={`group w-full text-left flex items-center gap-2.5 px-3 py-2 rounded-lg border transition-all duration-150 ${
className={`group flex w-full items-center gap-2.5 rounded-lg border px-3 py-2 text-left transition-all duration-150 ${
isOtherOn
? 'border-blue-300 dark:border-blue-600 bg-blue-50/80 dark:bg-blue-900/25 ring-1 ring-blue-200/50 dark:ring-blue-700/30'
: 'border-dashed border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50/60 dark:hover:bg-gray-750/50'
? 'border-blue-300 bg-blue-50/80 ring-1 ring-blue-200/50 dark:border-blue-600 dark:bg-blue-900/25 dark:ring-blue-700/30'
: 'dark:hover:bg-gray-750/50 border-dashed border-gray-200 hover:border-gray-300 hover:bg-gray-50/60 dark:border-gray-700/60 dark:hover:border-gray-600'
}`}
>
<kbd className={`flex-shrink-0 w-5 h-5 rounded text-[10px] font-mono flex items-center justify-center transition-all duration-150 ${
<kbd className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded font-mono text-[10px] transition-all duration-150 ${
isOtherOn
? 'bg-blue-500 dark:bg-blue-500 text-white font-semibold'
: 'bg-gray-100 dark:bg-gray-800 text-gray-400 dark:text-gray-500 border border-gray-200 dark:border-gray-700 group-hover:border-gray-300 dark:group-hover:border-gray-600'
? 'bg-blue-500 font-semibold text-white dark:bg-blue-500'
: 'border border-gray-200 bg-gray-100 text-gray-400 group-hover:border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500 dark:group-hover:border-gray-600'
}`}>
0
</kbd>
<span className={`text-[13px] leading-tight transition-colors ${
isOtherOn
? 'text-gray-900 dark:text-gray-100 font-medium'
? 'font-medium text-gray-900 dark:text-gray-100'
: 'text-gray-500 dark:text-gray-400'
}`}>
Other...
</span>
{isOtherOn && (
<svg className="w-4 h-4 text-blue-500 dark:text-blue-400 flex-shrink-0 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<svg className="ml-auto h-4 w-4 flex-shrink-0 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
)}
@@ -320,9 +320,9 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
e.stopPropagation();
}}
placeholder="Type your answer..."
className="w-full text-[13px] rounded-lg border-0 bg-gray-50 dark:bg-gray-900/60 text-gray-900 dark:text-gray-100 px-3 py-1.5 outline-none ring-1 ring-gray-200 dark:ring-gray-700 focus:ring-2 focus:ring-blue-400 dark:focus:ring-blue-500 placeholder:text-gray-400 dark:placeholder:text-gray-600 transition-shadow duration-200"
className="w-full rounded-lg border-0 bg-gray-50 px-3 py-1.5 text-[13px] text-gray-900 outline-none ring-1 ring-gray-200 transition-shadow duration-200 placeholder:text-gray-400 focus:ring-2 focus:ring-blue-400 dark:bg-gray-900/60 dark:text-gray-100 dark:ring-gray-700 dark:placeholder:text-gray-600 dark:focus:ring-blue-500"
/>
<kbd className="absolute right-2 top-1/2 -translate-y-1/2 text-[9px] font-mono text-gray-300 dark:text-gray-600 bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded border border-gray-200 dark:border-gray-700">
<kbd className="absolute right-2 top-1/2 -translate-y-1/2 rounded border border-gray-200 bg-gray-100 px-1 py-0.5 font-mono text-[9px] text-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-600">
Enter
</kbd>
</div>
@@ -332,11 +332,11 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
</div>
{/* Footer — compact */}
<div className="px-4 py-2 border-t border-gray-100 dark:border-gray-700/50 bg-gray-50/50 dark:bg-gray-800/50 flex items-center justify-between gap-2">
<div className="flex items-center justify-between gap-2 border-t border-gray-100 bg-gray-50/50 px-4 py-2 dark:border-gray-700/50 dark:bg-gray-800/50">
<button
type="button"
onClick={handleSkip}
className="text-[11px] text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
className="text-[11px] text-gray-400 transition-colors hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
>
{isSingle ? 'Skip' : 'Skip all'}
<span className="ml-1 text-[9px] text-gray-300 dark:text-gray-600">Esc</span>
@@ -347,9 +347,9 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
<button
type="button"
onClick={() => setCurrentStep(s => s - 1)}
className="inline-flex items-center gap-0.5 text-[11px] font-medium px-2.5 py-1.5 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700/60 transition-all duration-150"
className="inline-flex items-center gap-0.5 rounded-lg px-2.5 py-1.5 text-[11px] font-medium text-gray-600 transition-all duration-150 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700/60"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
Back
@@ -361,19 +361,19 @@ export const AskUserQuestionPanel: React.FC<PermissionPanelProps> = ({
type="button"
onClick={handleSubmit}
disabled={!hasCurrentSelection && !Object.keys(buildAnswers()).length}
className="inline-flex items-center gap-1 text-[11px] font-semibold px-3.5 py-1.5 rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 dark:from-blue-500 dark:to-blue-600 text-white shadow-sm hover:shadow-md disabled:opacity-30 disabled:cursor-not-allowed disabled:shadow-none transition-all duration-200"
className="inline-flex items-center gap-1 rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 px-3.5 py-1.5 text-[11px] font-semibold text-white shadow-sm transition-all duration-200 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-30 disabled:shadow-none dark:from-blue-500 dark:to-blue-600"
>
Submit
<span className="text-[9px] opacity-70 font-mono ml-0.5">Enter</span>
<span className="ml-0.5 font-mono text-[9px] opacity-70">Enter</span>
</button>
) : (
<button
type="button"
onClick={() => setCurrentStep(s => s + 1)}
className="inline-flex items-center gap-1 text-[11px] font-semibold px-3.5 py-1.5 rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 dark:from-blue-500 dark:to-blue-600 text-white shadow-sm hover:shadow-md transition-all duration-200"
className="inline-flex items-center gap-1 rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 px-3.5 py-1.5 text-[11px] font-semibold text-white shadow-sm transition-all duration-200 hover:shadow-md dark:from-blue-500 dark:to-blue-600"
>
Next
<span className="text-[9px] opacity-70 font-mono ml-0.5">Enter</span>
<span className="ml-0.5 font-mono text-[9px] opacity-70">Enter</span>
</button>
)}
</div>

View File

@@ -68,16 +68,16 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
const renderCopyButton = () => (
<button
onClick={handleAction}
className="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-all ml-1 flex-shrink-0"
className="ml-1 flex-shrink-0 text-gray-400 opacity-0 transition-all hover:text-gray-600 group-hover:opacity-100 dark:hover:text-gray-200"
title="Copy to clipboard"
aria-label="Copy to clipboard"
>
{copied ? (
<svg className="w-3 h-3 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3 w-3 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
)}
@@ -89,15 +89,15 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
return (
<div className="group my-1">
<div className="flex items-start gap-2">
<div className="flex items-center gap-1.5 flex-shrink-0 pt-0.5">
<svg className="w-3 h-3 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="flex flex-shrink-0 items-center gap-1.5 pt-0.5">
<svg className="h-3 w-3 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<div className="flex-1 min-w-0 flex items-start gap-2">
<div className="bg-gray-900 dark:bg-black rounded px-2.5 py-1 flex-1 min-w-0">
<code className={`text-xs text-green-400 font-mono ${wrapText ? 'whitespace-pre-wrap break-all' : 'block truncate'}`}>
<span className="text-green-600 dark:text-green-500 select-none">$ </span>{value}
<div className="flex min-w-0 flex-1 items-start gap-2">
<div className="min-w-0 flex-1 rounded bg-gray-900 px-2.5 py-1 dark:bg-black">
<code className={`font-mono text-xs text-green-400 ${wrapText ? 'whitespace-pre-wrap break-all' : 'block truncate'}`}>
<span className="select-none text-green-600 dark:text-green-500">$ </span>{value}
</code>
</div>
{action === 'copy' && renderCopyButton()}
@@ -105,7 +105,7 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
</div>
{secondary && (
<div className="ml-7 mt-1">
<span className="text-[11px] text-gray-400 dark:text-gray-500 italic">
<span className="text-[11px] italic text-gray-400 dark:text-gray-500">
{secondary}
</span>
</div>
@@ -118,12 +118,12 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
if (action === 'open-file') {
const displayName = value.split('/').pop() || value;
return (
<div className={`group flex items-center gap-1.5 border-l-2 ${colorScheme.border} pl-3 py-0.5 my-0.5`}>
<span className="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">{label || toolName}</span>
<span className="text-gray-300 dark:text-gray-600 text-[10px]">/</span>
<div className={`group flex items-center gap-1.5 border-l-2 ${colorScheme.border} my-0.5 py-0.5 pl-3`}>
<span className="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">{label || toolName}</span>
<span className="text-[10px] text-gray-300 dark:text-gray-600">/</span>
<button
onClick={handleAction}
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-mono hover:underline transition-colors truncate"
className="truncate font-mono text-xs text-blue-600 transition-colors hover:text-blue-700 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
title={value}
>
{displayName}
@@ -135,23 +135,23 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
// Search / jump-to-results style
if (action === 'jump-to-results') {
return (
<div className={`group flex items-center gap-1.5 border-l-2 ${colorScheme.border} pl-3 py-0.5 my-0.5`}>
<span className="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">{label || toolName}</span>
<span className="text-gray-300 dark:text-gray-600 text-[10px]">/</span>
<span className={`text-xs font-mono truncate flex-1 min-w-0 ${colorScheme.primary}`}>
<div className={`group flex items-center gap-1.5 border-l-2 ${colorScheme.border} my-0.5 py-0.5 pl-3`}>
<span className="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">{label || toolName}</span>
<span className="text-[10px] text-gray-300 dark:text-gray-600">/</span>
<span className={`min-w-0 flex-1 truncate font-mono text-xs ${colorScheme.primary}`}>
{value}
</span>
{secondary && (
<span className="text-[11px] text-gray-400 dark:text-gray-500 italic flex-shrink-0">
<span className="flex-shrink-0 text-[11px] italic text-gray-400 dark:text-gray-500">
{secondary}
</span>
)}
{toolResult && (
<a
href={`#tool-result-${toolId}`}
className="flex-shrink-0 text-[11px] text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors flex items-center gap-0.5"
className="flex flex-shrink-0 items-center gap-0.5 text-[11px] text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</a>
@@ -162,21 +162,21 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
// Default one-line style
return (
<div className={`group flex items-center gap-1.5 ${colorScheme.background || ''} border-l-2 ${colorScheme.border} pl-3 py-0.5 my-0.5`}>
<div className={`group flex items-center gap-1.5 ${colorScheme.background || ''} border-l-2 ${colorScheme.border} my-0.5 py-0.5 pl-3`}>
{icon && icon !== 'terminal' && (
<span className={`${colorScheme.icon} flex-shrink-0 text-xs`}>{icon}</span>
)}
{!icon && (label || toolName) && (
<span className="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">{label || toolName}</span>
<span className="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">{label || toolName}</span>
)}
{(icon || label || toolName) && (
<span className="text-gray-300 dark:text-gray-600 text-[10px]">/</span>
<span className="text-[10px] text-gray-300 dark:text-gray-600">/</span>
)}
<span className={`text-xs font-mono ${wrapText ? 'whitespace-pre-wrap break-all' : 'truncate'} flex-1 min-w-0 ${colorScheme.primary}`}>
<span className={`font-mono text-xs ${wrapText ? 'whitespace-pre-wrap break-all' : 'truncate'} min-w-0 flex-1 ${colorScheme.primary}`}>
{value}
</span>
{secondary && (
<span className={`text-[11px] ${colorScheme.secondary} italic flex-shrink-0`}>
<span className={`text-[11px] ${colorScheme.secondary} flex-shrink-0 italic`}>
{secondary}
</span>
)}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { CollapsibleSection } from './CollapsibleSection';
import type { SubagentChildTool } from '../../types/types';
import { CollapsibleSection } from './CollapsibleSection';
interface SubagentContainerProps {
toolInput: unknown;
@@ -57,7 +57,7 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
const title = `Subagent / ${subagentType}: ${description}`;
return (
<div className="border-l-2 border-l-purple-500 dark:border-l-purple-400 pl-3 py-0.5 my-1">
<div className="my-1 border-l-2 border-l-purple-500 py-0.5 pl-3 dark:border-l-purple-400">
<CollapsibleSection
title={title}
toolName="Task"
@@ -65,21 +65,21 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
>
{/* Prompt/request to the subagent */}
{prompt && (
<div className="text-xs text-gray-600 dark:text-gray-400 mb-2 whitespace-pre-wrap break-words line-clamp-4">
<div className="mb-2 line-clamp-4 whitespace-pre-wrap break-words text-xs text-gray-600 dark:text-gray-400">
{prompt}
</div>
)}
{/* Current tool indicator (while running) */}
{currentTool && !isComplete && (
<div className="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400 mt-1">
<span className="animate-pulse w-1.5 h-1.5 rounded-full bg-purple-500 dark:bg-purple-400 flex-shrink-0" />
<div className="mt-1 flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
<span className="h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-purple-500 dark:bg-purple-400" />
<span className="text-gray-400 dark:text-gray-500">Currently:</span>
<span className="font-medium text-gray-600 dark:text-gray-300">{currentTool.toolName}</span>
{getCompactToolDisplay(currentTool.toolName, currentTool.toolInput) && (
<>
<span className="text-gray-300 dark:text-gray-600">/</span>
<span className="font-mono truncate text-gray-500 dark:text-gray-400">
<span className="truncate font-mono text-gray-500 dark:text-gray-400">
{getCompactToolDisplay(currentTool.toolName, currentTool.toolInput)}
</span>
</>
@@ -89,8 +89,8 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
{/* Completion status */}
{isComplete && (
<div className="flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400 mt-1">
<svg className="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="mt-1 flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400">
<svg className="h-3 w-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span>Completed ({childTools.length} {childTools.length === 1 ? 'tool' : 'tools'})</span>
@@ -99,10 +99,10 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
{/* Tool history (collapsed) */}
{childTools.length > 0 && (
<details className="mt-2 group/history">
<summary className="cursor-pointer text-[11px] text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 flex items-center gap-1">
<details className="group/history mt-2">
<summary className="flex cursor-pointer items-center gap-1 text-[11px] text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300">
<svg
className="w-2.5 h-2.5 transition-transform duration-150 group-open/history:rotate-90 flex-shrink-0"
className="h-2.5 w-2.5 flex-shrink-0 transition-transform duration-150 group-open/history:rotate-90"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -111,18 +111,18 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
</svg>
<span>View tool history ({childTools.length})</span>
</summary>
<div className="mt-1 pl-3 border-l border-gray-200 dark:border-gray-700 space-y-0.5">
<div className="mt-1 space-y-0.5 border-l border-gray-200 pl-3 dark:border-gray-700">
{childTools.map((child, index) => (
<div key={child.toolId} className="flex items-center gap-1.5 text-[11px] text-gray-500 dark:text-gray-400">
<span className="text-gray-400 dark:text-gray-500 w-4 text-right flex-shrink-0">{index + 1}.</span>
<span className="w-4 flex-shrink-0 text-right text-gray-400 dark:text-gray-500">{index + 1}.</span>
<span className="font-medium">{child.toolName}</span>
{getCompactToolDisplay(child.toolName, child.toolInput) && (
<span className="font-mono truncate text-gray-400 dark:text-gray-500">
<span className="truncate font-mono text-gray-400 dark:text-gray-500">
{getCompactToolDisplay(child.toolName, child.toolInput)}
</span>
)}
{child.toolResult?.isError && (
<span className="text-red-500 flex-shrink-0">(error)</span>
<span className="flex-shrink-0 text-red-500">(error)</span>
)}
</div>
))}
@@ -163,11 +163,11 @@ export const SubagentContainer: React.FC<SubagentContainerProps> = ({
}
return typeof content === 'string' ? (
<div className="whitespace-pre-wrap break-words line-clamp-6">
<div className="line-clamp-6 whitespace-pre-wrap break-words">
{content}
</div>
) : content ? (
<pre className="whitespace-pre-wrap break-words line-clamp-6 font-mono text-[11px]">
<pre className="line-clamp-6 whitespace-pre-wrap break-words font-mono text-[11px]">
{JSON.stringify(content, null, 2)}
</pre>
) : null;

View File

@@ -38,44 +38,44 @@ export const ToolDiffViewer: React.FC<ToolDiffViewerProps> = ({
);
return (
<div className="border border-gray-200/60 dark:border-gray-700/50 rounded overflow-hidden">
<div className="overflow-hidden rounded border border-gray-200/60 dark:border-gray-700/50">
{/* Header */}
<div className="flex items-center justify-between px-2.5 py-1 bg-gray-50/80 dark:bg-gray-800/40 border-b border-gray-200/60 dark:border-gray-700/50">
<div className="flex items-center justify-between border-b border-gray-200/60 bg-gray-50/80 px-2.5 py-1 dark:border-gray-700/50 dark:bg-gray-800/40">
{onFileClick ? (
<button
onClick={onFileClick}
className="text-[11px] font-mono text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 truncate cursor-pointer transition-colors"
className="cursor-pointer truncate font-mono text-[11px] text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
{filePath}
</button>
) : (
<span className="text-[11px] font-mono text-gray-600 dark:text-gray-400 truncate">
<span className="truncate font-mono text-[11px] text-gray-600 dark:text-gray-400">
{filePath}
</span>
)}
<span className={`text-[10px] font-medium px-1.5 py-px rounded ${badgeClasses} flex-shrink-0 ml-2`}>
<span className={`rounded px-1.5 py-px text-[10px] font-medium ${badgeClasses} ml-2 flex-shrink-0`}>
{badge}
</span>
</div>
{/* Diff lines */}
<div className="text-[11px] font-mono leading-[18px]">
<div className="font-mono text-[11px] leading-[18px]">
{diffLines.map((diffLine, i) => (
<div key={i} className="flex">
<span
className={`w-6 text-center select-none flex-shrink-0 ${
className={`w-6 flex-shrink-0 select-none text-center ${
diffLine.type === 'removed'
? 'bg-red-50 dark:bg-red-950/30 text-red-400 dark:text-red-500'
: 'bg-green-50 dark:bg-green-950/30 text-green-400 dark:text-green-500'
? 'bg-red-50 text-red-400 dark:bg-red-950/30 dark:text-red-500'
: 'bg-green-50 text-green-400 dark:bg-green-950/30 dark:text-green-500'
}`}
>
{diffLine.type === 'removed' ? '-' : '+'}
</span>
<span
className={`px-2 flex-1 whitespace-pre-wrap ${
className={`flex-1 whitespace-pre-wrap px-2 ${
diffLine.type === 'removed'
? 'bg-red-50/50 dark:bg-red-950/20 text-red-800 dark:text-red-200'
: 'bg-green-50/50 dark:bg-green-950/20 text-green-800 dark:text-green-200'
? 'bg-red-50/50 text-red-800 dark:bg-red-950/20 dark:text-red-200'
: 'bg-green-50/50 text-green-800 dark:bg-green-950/20 dark:text-green-200'
}`}
>
{diffLine.content}

View File

@@ -1,15 +1,14 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { QuickSettingsPanel } from '../../quick-settings-panel';
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
import { useTranslation } from 'react-i18next';
import ChatMessagesPane from './subcomponents/ChatMessagesPane';
import ChatComposer from './subcomponents/ChatComposer';
import type { ChatInterfaceProps } from '../types/types';
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
import { QuickSettingsPanel } from '../../quick-settings-panel';
import type { ChatInterfaceProps, Provider } from '../types/types';
import { useChatProviderState } from '../hooks/useChatProviderState';
import { useChatSessionState } from '../hooks/useChatSessionState';
import { useChatRealtimeHandlers } from '../hooks/useChatRealtimeHandlers';
import { useChatComposerState } from '../hooks/useChatComposerState';
import type { Provider } from '../types/types';
import ChatMessagesPane from './subcomponents/ChatMessagesPane';
import ChatComposer from './subcomponents/ChatComposer';
type PendingViewSession = {
@@ -259,7 +258,7 @@ function ChatInterface({
: t('messageTypes.claude');
return (
<div className="flex items-center justify-center h-full">
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<p className="text-sm">
{t('projectSelection.startChatWithProvider', {
@@ -274,7 +273,7 @@ function ChatInterface({
return (
<>
<div className="h-full flex flex-col">
<div className="flex h-full flex-col">
<ChatMessagesPane
scrollContainerRef={scrollContainerRef}
onWheel={handleScroll}

View File

@@ -10,15 +10,15 @@ export default function AssistantThinkingIndicator({ selectedProvider }: Assista
return (
<div className="chat-message assistant">
<div className="w-full">
<div className="flex items-center space-x-3 mb-2">
<div className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm flex-shrink-0 p-1 bg-transparent">
<SessionProviderLogo provider={selectedProvider} className="w-full h-full" />
<div className="mb-2 flex items-center space-x-3">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-transparent p-1 text-sm text-white">
<SessionProviderLogo provider={selectedProvider} className="h-full w-full" />
</div>
<div className="text-sm font-medium text-gray-900 dark:text-white">
{selectedProvider === 'cursor' ? 'Cursor' : selectedProvider === 'codex' ? 'Codex' : selectedProvider === 'gemini' ? 'Gemini' : 'Claude'}
</div>
</div>
<div className="w-full text-sm text-gray-500 dark:text-gray-400 pl-3 sm:pl-0">
<div className="w-full pl-3 text-sm text-gray-500 dark:text-gray-400 sm:pl-0">
<div className="flex items-center space-x-1">
<div className="animate-pulse">.</div>
<div className="animate-pulse" style={{ animationDelay: '0.2s' }}>

View File

@@ -1,9 +1,3 @@
import CommandMenu from './CommandMenu';
import ClaudeStatus from './ClaudeStatus';
import MicButton from '../../../mic-button/view/MicButton';
import ImageAttachment from './ImageAttachment';
import PermissionRequestsBanner from './PermissionRequestsBanner';
import ChatInputControls from './ChatInputControls';
import { useTranslation } from 'react-i18next';
import type {
ChangeEvent,
@@ -17,7 +11,13 @@ import type {
SetStateAction,
TouchEvent,
} from 'react';
import MicButton from '../../../mic-button/view/MicButton';
import type { PendingPermissionRequest, PermissionMode, Provider } from '../../types/types';
import CommandMenu from './CommandMenu';
import ClaudeStatus from './ClaudeStatus';
import ImageAttachment from './ImageAttachment';
import PermissionRequestsBanner from './PermissionRequestsBanner';
import ChatInputControls from './ChatInputControls';
interface MentionableFile {
name: string;
@@ -169,7 +169,7 @@ export default function ChatComposer({
: '';
return (
<div className={`p-2 sm:p-4 md:p-4 flex-shrink-0 pb-2 sm:pb-4 md:pb-6 ${mobileFloatingClass}`}>
<div className={`flex-shrink-0 p-2 pb-2 sm:p-4 sm:pb-4 md:p-4 md:pb-6 ${mobileFloatingClass}`}>
{!hasQuestionPanel && (
<div className="flex-1">
<ClaudeStatus
@@ -181,7 +181,7 @@ export default function ChatComposer({
</div>
)}
<div className="max-w-4xl mx-auto mb-3">
<div className="mx-auto mb-3 max-w-4xl">
<PermissionRequestsBanner
pendingPermissionRequests={pendingPermissionRequests}
handlePermissionDecision={handlePermissionDecision}
@@ -205,11 +205,11 @@ export default function ChatComposer({
/>}
</div>
{!hasQuestionPanel && <form onSubmit={onSubmit as (event: FormEvent<HTMLFormElement>) => void} className="relative max-w-4xl mx-auto">
{!hasQuestionPanel && <form onSubmit={onSubmit as (event: FormEvent<HTMLFormElement>) => void} className="relative mx-auto max-w-4xl">
{isDragActive && (
<div className="absolute inset-0 bg-primary/15 border-2 border-dashed border-primary/50 rounded-2xl flex items-center justify-center z-50">
<div className="bg-card rounded-xl p-4 shadow-lg border border-border/30">
<svg className="w-8 h-8 text-primary mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-primary/15">
<div className="rounded-xl border border-border/30 bg-card p-4 shadow-lg">
<svg className="mx-auto mb-2 h-8 w-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -223,7 +223,7 @@ export default function ChatComposer({
)}
{attachedImages.length > 0 && (
<div className="mb-2 p-2 bg-muted/40 rounded-xl">
<div className="mb-2 rounded-xl bg-muted/40 p-2">
<div className="flex flex-wrap gap-2">
{attachedImages.map((file, index) => (
<ImageAttachment
@@ -239,14 +239,14 @@ export default function ChatComposer({
)}
{showFileDropdown && filteredFiles.length > 0 && (
<div className="absolute bottom-full left-0 right-0 mb-2 bg-card/95 backdrop-blur-md border border-border/50 rounded-xl shadow-lg max-h-48 overflow-y-auto z-50">
<div className="absolute bottom-full left-0 right-0 z-50 mb-2 max-h-48 overflow-y-auto rounded-xl border border-border/50 bg-card/95 shadow-lg backdrop-blur-md">
{filteredFiles.map((file, index) => (
<div
key={file.path}
className={`px-4 py-3 cursor-pointer border-b border-border/30 last:border-b-0 touch-manipulation ${
className={`cursor-pointer touch-manipulation border-b border-border/30 px-4 py-3 last:border-b-0 ${
index === selectedFileIndex
? 'bg-primary/8 text-primary'
: 'hover:bg-accent/50 text-foreground'
: 'text-foreground hover:bg-accent/50'
}`}
onMouseDown={(event) => {
event.preventDefault();
@@ -258,8 +258,8 @@ export default function ChatComposer({
onSelectFile(file);
}}
>
<div className="font-medium text-sm">{file.name}</div>
<div className="text-xs text-muted-foreground font-mono">{file.path}</div>
<div className="text-sm font-medium">{file.name}</div>
<div className="font-mono text-xs text-muted-foreground">{file.path}</div>
</div>
))}
</div>
@@ -277,13 +277,13 @@ export default function ChatComposer({
<div
{...getRootProps()}
className={`relative bg-card/80 backdrop-blur-sm rounded-2xl shadow-sm border border-border/50 focus-within:shadow-md focus-within:border-primary/30 focus-within:ring-1 focus-within:ring-primary/15 transition-all duration-200 overflow-hidden ${
className={`relative overflow-hidden rounded-2xl border border-border/50 bg-card/80 shadow-sm backdrop-blur-sm transition-all duration-200 focus-within:border-primary/30 focus-within:shadow-md focus-within:ring-1 focus-within:ring-primary/15 ${
isTextareaExpanded ? 'chat-input-expanded' : ''
}`}
>
<input {...getInputProps()} />
<div ref={inputHighlightRef} aria-hidden="true" className="absolute inset-0 pointer-events-none overflow-hidden rounded-2xl">
<div className="chat-input-placeholder block w-full pl-12 pr-20 sm:pr-40 py-1.5 sm:py-4 text-transparent text-base leading-6 whitespace-pre-wrap break-words">
<div ref={inputHighlightRef} aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden rounded-2xl">
<div className="chat-input-placeholder block w-full whitespace-pre-wrap break-words py-1.5 pl-12 pr-20 text-base leading-6 text-transparent sm:py-4 sm:pr-40">
{renderInputWithMentions(input)}
</div>
</div>
@@ -302,17 +302,17 @@ export default function ChatComposer({
onInput={onTextareaInput}
placeholder={placeholder}
disabled={isLoading}
className="chat-input-placeholder block w-full pl-12 pr-20 sm:pr-40 py-1.5 sm:py-4 bg-transparent rounded-2xl focus:outline-none text-foreground placeholder-muted-foreground/50 disabled:opacity-50 resize-none min-h-[50px] sm:min-h-[80px] max-h-[40vh] sm:max-h-[300px] overflow-y-auto text-base leading-6 transition-all duration-200"
className="chat-input-placeholder block max-h-[40vh] min-h-[50px] w-full resize-none overflow-y-auto rounded-2xl bg-transparent py-1.5 pl-12 pr-20 text-base leading-6 text-foreground placeholder-muted-foreground/50 transition-all duration-200 focus:outline-none disabled:opacity-50 sm:max-h-[300px] sm:min-h-[80px] sm:py-4 sm:pr-40"
style={{ height: '50px' }}
/>
<button
type="button"
onClick={openImagePicker}
className="absolute left-2 top-1/2 transform -translate-y-1/2 p-2 hover:bg-accent/60 rounded-xl transition-colors"
className="absolute left-2 top-1/2 -translate-y-1/2 transform rounded-xl p-2 transition-colors hover:bg-accent/60"
title={t('input.attachImages')}
>
<svg className="w-5 h-5 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-5 w-5 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -322,8 +322,8 @@ export default function ChatComposer({
</svg>
</button>
<div className="absolute right-16 sm:right-16 top-1/2 transform -translate-y-1/2" style={{ display: 'none' }}>
<MicButton onTranscript={onTranscript} className="w-10 h-10 sm:w-10 sm:h-10" />
<div className="absolute right-16 top-1/2 -translate-y-1/2 transform sm:right-16" style={{ display: 'none' }}>
<MicButton onTranscript={onTranscript} className="h-10 w-10 sm:h-10 sm:w-10" />
</div>
<button
@@ -337,15 +337,15 @@ export default function ChatComposer({
event.preventDefault();
onSubmit(event);
}}
className="absolute right-2 top-1/2 transform -translate-y-1/2 w-10 h-10 sm:w-11 sm:h-11 bg-primary hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed rounded-xl flex items-center justify-center transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:ring-offset-1 focus:ring-offset-background"
className="absolute right-2 top-1/2 flex h-10 w-10 -translate-y-1/2 transform items-center justify-center rounded-xl bg-primary transition-all duration-200 hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:ring-offset-1 focus:ring-offset-background disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground sm:h-11 sm:w-11"
>
<svg className="w-4 h-4 sm:w-[18px] sm:h-[18px] text-primary-foreground transform rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4 rotate-90 transform text-primary-foreground sm:h-[18px] sm:w-[18px]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</button>
<div
className={`absolute bottom-1 left-12 right-14 sm:right-40 text-xs text-muted-foreground/50 pointer-events-none hidden sm:block transition-opacity duration-200 ${
className={`pointer-events-none absolute bottom-1 left-12 right-14 hidden text-xs text-muted-foreground/50 transition-opacity duration-200 sm:right-40 sm:block ${
input.trim() ? 'opacity-0' : 'opacity-100'
}`}
>

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import type { PermissionMode, Provider } from '../../types/types';
import ThinkingModeSelector from './ThinkingModeSelector';
import TokenUsagePie from './TokenUsagePie';
import type { PermissionMode, Provider } from '../../types/types';
interface ChatInputControlsProps {
permissionMode: PermissionMode | string;
@@ -38,24 +38,24 @@ export default function ChatInputControls({
const { t } = useTranslation('chat');
return (
<div className="flex items-center justify-center gap-2 sm:gap-3 flex-wrap">
<div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3">
<button
type="button"
onClick={onModeSwitch}
className={`px-2.5 py-1 sm:px-3 sm:py-1.5 rounded-lg text-sm font-medium border transition-all duration-200 ${
className={`rounded-lg border px-2.5 py-1 text-sm font-medium transition-all duration-200 sm:px-3 sm:py-1.5 ${
permissionMode === 'default'
? 'bg-muted/50 text-muted-foreground border-border/60 hover:bg-muted'
? 'border-border/60 bg-muted/50 text-muted-foreground hover:bg-muted'
: permissionMode === 'acceptEdits'
? 'bg-green-50 dark:bg-green-900/15 text-green-700 dark:text-green-300 border-green-300/60 dark:border-green-600/40 hover:bg-green-100 dark:hover:bg-green-900/25'
? 'border-green-300/60 bg-green-50 text-green-700 hover:bg-green-100 dark:border-green-600/40 dark:bg-green-900/15 dark:text-green-300 dark:hover:bg-green-900/25'
: permissionMode === 'bypassPermissions'
? 'bg-orange-50 dark:bg-orange-900/15 text-orange-700 dark:text-orange-300 border-orange-300/60 dark:border-orange-600/40 hover:bg-orange-100 dark:hover:bg-orange-900/25'
: 'bg-primary/5 text-primary border-primary/20 hover:bg-primary/10'
? 'border-orange-300/60 bg-orange-50 text-orange-700 hover:bg-orange-100 dark:border-orange-600/40 dark:bg-orange-900/15 dark:text-orange-300 dark:hover:bg-orange-900/25'
: 'border-primary/20 bg-primary/5 text-primary hover:bg-primary/10'
}`}
title={t('input.clickToChangeMode')}
>
<div className="flex items-center gap-1.5">
<div
className={`w-1.5 h-1.5 rounded-full ${
className={`h-1.5 w-1.5 rounded-full ${
permissionMode === 'default'
? 'bg-muted-foreground'
: permissionMode === 'acceptEdits'
@@ -83,10 +83,10 @@ export default function ChatInputControls({
<button
type="button"
onClick={onToggleCommandMenu}
className="relative w-7 h-7 sm:w-8 sm:h-8 text-muted-foreground hover:text-foreground rounded-lg flex items-center justify-center transition-colors hover:bg-accent/60"
className="relative flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground sm:h-8 sm:w-8"
title={t('input.showAllCommands')}
>
<svg className="w-4 h-4 sm:w-5 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4 sm:h-5 sm:w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -96,7 +96,7 @@ export default function ChatInputControls({
</svg>
{slashCommandsCount > 0 && (
<span
className="absolute -top-1 -right-1 bg-primary text-primary-foreground text-[10px] font-bold rounded-full w-4 h-4 sm:w-5 sm:h-5 flex items-center justify-center"
className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground sm:h-5 sm:w-5"
>
{slashCommandsCount}
</span>
@@ -107,11 +107,11 @@ export default function ChatInputControls({
<button
type="button"
onClick={onClearInput}
className="w-7 h-7 sm:w-8 sm:h-8 bg-card hover:bg-accent/60 border border-border/50 rounded-lg flex items-center justify-center transition-all duration-200 group shadow-sm"
className="group flex h-7 w-7 items-center justify-center rounded-lg border border-border/50 bg-card shadow-sm transition-all duration-200 hover:bg-accent/60 sm:h-8 sm:w-8"
title={t('input.clearInput', { defaultValue: 'Clear input' })}
>
<svg
className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-muted-foreground group-hover:text-foreground transition-colors"
className="h-3.5 w-3.5 text-muted-foreground transition-colors group-hover:text-foreground sm:h-4 sm:w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -124,10 +124,10 @@ export default function ChatInputControls({
{isUserScrolledUp && hasMessages && (
<button
onClick={onScrollToBottom}
className="w-7 h-7 sm:w-8 sm:h-8 bg-primary hover:bg-primary/90 text-primary-foreground rounded-lg shadow-sm flex items-center justify-center transition-all duration-200 hover:scale-105"
className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-primary-foreground shadow-sm transition-all duration-200 hover:scale-105 hover:bg-primary/90 sm:h-8 sm:w-8"
title={t('input.scrollToBottom', { defaultValue: 'Scroll to bottom' })}
>
<svg className="w-3.5 h-3.5 sm:w-4 sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3.5 w-3.5 sm:h-4 sm:w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</button>

View File

@@ -1,13 +1,12 @@
import { useTranslation } from 'react-i18next';
import { useCallback, useRef } from 'react';
import type { Dispatch, RefObject, SetStateAction } from 'react';
import MessageComponent from './MessageComponent';
import ProviderSelectionEmptyState from './ProviderSelectionEmptyState';
import type { ChatMessage } from '../../types/types';
import type { Project, ProjectSession, SessionProvider } from '../../../../types/app';
import AssistantThinkingIndicator from './AssistantThinkingIndicator';
import { getIntrinsicMessageKey } from '../../utils/messageKeys';
import MessageComponent from './MessageComponent';
import ProviderSelectionEmptyState from './ProviderSelectionEmptyState';
import AssistantThinkingIndicator from './AssistantThinkingIndicator';
interface ChatMessagesPaneProps {
scrollContainerRef: RefObject<HTMLDivElement>;
@@ -134,12 +133,12 @@ export default function ChatMessagesPane({
ref={scrollContainerRef}
onWheel={onWheel}
onTouchMove={onTouchMove}
className="flex-1 overflow-y-auto overflow-x-hidden px-0 py-3 sm:p-4 space-y-3 sm:space-y-4 relative"
className="relative flex-1 space-y-3 overflow-y-auto overflow-x-hidden px-0 py-3 sm:space-y-4 sm:p-4"
>
{isLoadingSessionMessages && chatMessages.length === 0 ? (
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
<div className="mt-8 text-center text-gray-500 dark:text-gray-400">
<div className="flex items-center justify-center space-x-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400" />
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-gray-400" />
<p>{t('session.loading.sessionMessages')}</p>
</div>
</div>
@@ -167,9 +166,9 @@ export default function ChatMessagesPane({
<>
{/* Loading indicator for older messages (hide when load-all is active) */}
{isLoadingMoreMessages && !isLoadingAllMessages && !allMessagesLoaded && (
<div className="text-center text-gray-500 dark:text-gray-400 py-3">
<div className="py-3 text-center text-gray-500 dark:text-gray-400">
<div className="flex items-center justify-center space-x-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400" />
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-gray-400" />
<p className="text-sm">{t('session.loading.olderMessages')}</p>
</div>
</div>
@@ -177,7 +176,7 @@ export default function ChatMessagesPane({
{/* Indicator showing there are more messages to load (hide when all loaded) */}
{hasMoreMessages && !isLoadingMoreMessages && !allMessagesLoaded && (
<div className="text-center text-gray-500 dark:text-gray-400 text-sm py-2 border-b border-gray-200 dark:border-gray-700">
<div className="border-b border-gray-200 py-2 text-center text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400">
{totalMessages > 0 && (
<span>
{t('session.messages.showingOf', { shown: sessionMessagesCount, total: totalMessages })}{' '}
@@ -189,22 +188,22 @@ export default function ChatMessagesPane({
{/* Floating "Load all messages" overlay */}
{(showLoadAllOverlay || isLoadingAllMessages || loadAllJustFinished) && (
<div className="sticky top-2 z-20 flex justify-center pointer-events-none">
<div className="pointer-events-none sticky top-2 z-20 flex justify-center">
{loadAllJustFinished ? (
<div className="px-4 py-1.5 text-xs font-medium text-white bg-green-600 dark:bg-green-500 rounded-full shadow-lg flex items-center space-x-2">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="flex items-center space-x-2 rounded-full bg-green-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg dark:bg-green-500">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
<span>{t('session.messages.allLoaded')}</span>
</div>
) : (
<button
className="pointer-events-auto px-4 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded-full shadow-lg transition-all duration-200 hover:scale-105 disabled:opacity-75 disabled:cursor-wait flex items-center space-x-2"
className="pointer-events-auto flex items-center space-x-2 rounded-full bg-blue-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg transition-all duration-200 hover:scale-105 hover:bg-blue-700 disabled:cursor-wait disabled:opacity-75 dark:bg-blue-500 dark:hover:bg-blue-600"
onClick={loadAllMessages}
disabled={isLoadingAllMessages}
>
{isLoadingAllMessages && (
<div className="animate-spin rounded-full h-3 w-3 border-2 border-white/30 border-t-white" />
<div className="h-3 w-3 animate-spin rounded-full border-2 border-white/30 border-t-white" />
)}
<span>
{isLoadingAllMessages
@@ -219,21 +218,21 @@ export default function ChatMessagesPane({
{/* Performance warning when all messages are loaded */}
{allMessagesLoaded && (
<div className="text-center text-amber-600 dark:text-amber-400 text-xs py-1.5 bg-amber-50 dark:bg-amber-900/20 border-b border-amber-200 dark:border-amber-800">
<div className="border-b border-amber-200 bg-amber-50 py-1.5 text-center text-xs text-amber-600 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-400">
{t('session.messages.perfWarning')}
</div>
)}
{/* Legacy message count indicator (for non-paginated view) */}
{!hasMoreMessages && chatMessages.length > visibleMessageCount && (
<div className="text-center text-gray-500 dark:text-gray-400 text-sm py-2 border-b border-gray-200 dark:border-gray-700">
<div className="border-b border-gray-200 py-2 text-center text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400">
{t('session.messages.showingLast', { count: visibleMessageCount, total: chatMessages.length })} |
<button className="ml-1 text-blue-600 hover:text-blue-700 underline" onClick={loadEarlierMessages}>
<button className="ml-1 text-blue-600 underline hover:text-blue-700" onClick={loadEarlierMessages}>
{t('session.messages.loadEarlier')}
</button>
{' | '}
<button
className="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline"
className="text-blue-600 underline hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
onClick={loadAllMessages}
>
{t('session.messages.loadAll')}

View File

@@ -109,8 +109,8 @@ export default function ClaudeStatus({
: t('claudeStatus.elapsed.startingNow', { defaultValue: 'Starting now' });
return (
<div className="w-full mb-3 sm:mb-6 animate-in slide-in-from-bottom duration-300">
<div className="relative max-w-4xl mx-auto overflow-hidden rounded-2xl border border-border/70 bg-card/90 shadow-md backdrop-blur-md">
<div className="animate-in slide-in-from-bottom mb-3 w-full duration-300 sm:mb-6">
<div className="relative mx-auto max-w-4xl overflow-hidden rounded-2xl border border-border/70 bg-card/90 shadow-md backdrop-blur-md">
<div className="pointer-events-none absolute inset-0 bg-gradient-to-r from-primary/10 via-transparent to-sky-500/10 dark:from-primary/20 dark:to-sky-400/20" />
<div className="relative px-3 py-3 sm:px-4 sm:py-3.5">
@@ -160,7 +160,7 @@ export default function ClaudeStatus({
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[11px] text-muted-foreground sm:text-xs">
<span
aria-hidden="true"
className="inline-flex items-center -ml-2 rounded-full border border-border/70 bg-background/60 px-2 py-0.5"
className="-ml-2 inline-flex items-center rounded-full border border-border/70 bg-background/60 px-2 py-0.5"
>
{elapsedLabel}
</span>
@@ -173,7 +173,7 @@ export default function ClaudeStatus({
<button
type="button"
onClick={onAbort}
className="inline-flex w-full items-center justify-center gap-2 rounded-xl bg-destructive px-3.5 py-2 text-sm font-semibold text-destructive-foreground shadow-sm ring-1 ring-destructive/40 transition-opacity hover:opacity-95 active:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/70 sm:w-auto"
className="inline-flex w-full items-center justify-center gap-2 rounded-xl bg-destructive px-3.5 py-2 text-sm font-semibold text-destructive-foreground shadow-sm ring-1 ring-destructive/40 transition-opacity hover:opacity-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/70 active:opacity-90 sm:w-auto"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />

View File

@@ -17,16 +17,16 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }: ImageAttachm
}, [file]);
return (
<div className="relative group">
<img src={preview} alt={file.name} className="w-20 h-20 object-cover rounded" />
<div className="group relative">
<img src={preview} alt={file.name} className="h-20 w-20 rounded object-cover" />
{uploadProgress !== undefined && uploadProgress < 100 && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<div className="text-white text-xs">{uploadProgress}%</div>
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<div className="text-xs text-white">{uploadProgress}%</div>
</div>
)}
{error && (
<div className="absolute inset-0 bg-red-500/50 flex items-center justify-center">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="absolute inset-0 flex items-center justify-center bg-red-500/50">
<svg className="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
@@ -34,10 +34,10 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }: ImageAttachm
<button
type="button"
onClick={onRemove}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 focus:opacity-100 transition-opacity"
className="absolute -right-2 -top-2 rounded-full bg-red-500 p-1 text-white opacity-100 transition-opacity focus:opacity-100 sm:opacity-0 sm:group-hover:opacity-100"
aria-label="Remove image"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>

View File

@@ -32,7 +32,7 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
if (shouldInline) {
return (
<code
className={`font-mono text-[0.9em] px-1.5 py-0.5 rounded-md bg-gray-100 text-gray-900 border border-gray-200 dark:bg-gray-800/60 dark:text-gray-100 dark:border-gray-700 whitespace-pre-wrap break-words ${className || ''
className={`whitespace-pre-wrap break-words rounded-md border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[0.9em] text-gray-900 dark:border-gray-700 dark:bg-gray-800/60 dark:text-gray-100 ${className || ''
}`}
{...props}
>
@@ -45,9 +45,9 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
const language = match ? match[1] : 'text';
return (
<div className="relative group my-2">
<div className="group relative my-2">
{language && language !== 'text' && (
<div className="absolute top-2 left-3 z-10 text-xs text-gray-400 font-medium uppercase">{language}</div>
<div className="absolute left-3 top-2 z-10 text-xs font-medium uppercase text-gray-400">{language}</div>
)}
<button
@@ -60,13 +60,13 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
}
})
}
className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 focus:opacity-100 active:opacity-100 transition-opacity text-xs px-2 py-1 rounded-md bg-gray-700/80 hover:bg-gray-700 text-white border border-gray-600"
className="absolute right-2 top-2 z-10 rounded-md border border-gray-600 bg-gray-700/80 px-2 py-1 text-xs text-white opacity-0 transition-opacity hover:bg-gray-700 focus:opacity-100 active:opacity-100 group-hover:opacity-100"
title={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
aria-label={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
>
{copied ? (
<span className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
<svg className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
@@ -78,7 +78,7 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
) : (
<span className="flex items-center gap-1">
<svg
className="w-3.5 h-3.5"
className="h-3.5 w-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@@ -119,27 +119,27 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
const markdownComponents = {
code: CodeBlock,
blockquote: ({ children }: { children?: React.ReactNode }) => (
<blockquote className="border-l-4 border-gray-300 dark:border-gray-600 pl-4 italic text-gray-600 dark:text-gray-400 my-2">
<blockquote className="my-2 border-l-4 border-gray-300 pl-4 italic text-gray-600 dark:border-gray-600 dark:text-gray-400">
{children}
</blockquote>
),
a: ({ href, children }: { href?: string; children?: React.ReactNode }) => (
<a href={href} className="text-blue-600 dark:text-blue-400 hover:underline" target="_blank" rel="noopener noreferrer">
<a href={href} className="text-blue-600 hover:underline dark:text-blue-400" target="_blank" rel="noopener noreferrer">
{children}
</a>
),
p: ({ children }: { children?: React.ReactNode }) => <div className="mb-2 last:mb-0">{children}</div>,
table: ({ children }: { children?: React.ReactNode }) => (
<div className="overflow-x-auto my-2">
<div className="my-2 overflow-x-auto">
<table className="min-w-full border-collapse border border-gray-200 dark:border-gray-700">{children}</table>
</div>
),
thead: ({ children }: { children?: React.ReactNode }) => <thead className="bg-gray-50 dark:bg-gray-800">{children}</thead>,
th: ({ children }: { children?: React.ReactNode }) => (
<th className="px-3 py-2 text-left text-sm font-semibold border border-gray-200 dark:border-gray-700">{children}</th>
<th className="border border-gray-200 px-3 py-2 text-left text-sm font-semibold dark:border-gray-700">{children}</th>
),
td: ({ children }: { children?: React.ReactNode }) => (
<td className="px-3 py-2 align-top text-sm border border-gray-200 dark:border-gray-700">{children}</td>
<td className="border border-gray-200 px-3 py-2 align-top text-sm dark:border-gray-700">{children}</td>
),
};

View File

@@ -7,12 +7,12 @@ import type {
PermissionGrantResult,
Provider,
} from '../../types/types';
import { Markdown } from './Markdown';
import { formatUsageLimitText } from '../../utils/chatFormatting';
import { getClaudePermissionSuggestion } from '../../utils/chatPermissions';
import { copyTextToClipboard } from '../../../../utils/clipboard';
import type { Project } from '../../../../types/app';
import { ToolRenderer, shouldHideToolResult } from '../../tools';
import { Markdown } from './Markdown';
type DiffLine = {
type: string;
@@ -100,9 +100,9 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
>
{message.type === 'user' ? (
/* User message bubble on the right */
<div className="flex items-end space-x-0 sm:space-x-3 w-full sm:w-auto sm:max-w-[85%] md:max-w-md lg:max-w-lg xl:max-w-xl">
<div className="bg-blue-600 text-white rounded-2xl rounded-br-md px-3 sm:px-4 py-2 shadow-sm flex-1 sm:flex-initial group">
<div className="text-sm whitespace-pre-wrap break-words">
<div className="flex w-full items-end space-x-0 sm:w-auto sm:max-w-[85%] sm:space-x-3 md:max-w-md lg:max-w-lg xl:max-w-xl">
<div className="group flex-1 rounded-2xl rounded-br-md bg-blue-600 px-3 py-2 text-white shadow-sm sm:flex-initial sm:px-4">
<div className="whitespace-pre-wrap break-words text-sm">
{message.content}
</div>
{message.images && message.images.length > 0 && (
@@ -112,13 +112,13 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
key={img.name || idx}
src={img.data}
alt={img.name}
className="rounded-lg max-w-full h-auto cursor-pointer hover:opacity-90 transition-opacity"
className="h-auto max-w-full cursor-pointer rounded-lg transition-opacity hover:opacity-90"
onClick={() => window.open(img.data, '_blank')}
/>
))}
</div>
)}
<div className="flex items-center justify-end gap-1 mt-1 text-xs text-blue-100">
<div className="mt-1 flex items-center justify-end gap-1 text-xs text-blue-100">
<button
type="button"
onClick={() => {
@@ -134,7 +134,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
aria-label={messageCopied ? t('copyMessage.copied') : t('copyMessage.copy')}
>
{messageCopied ? (
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
<svg className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
@@ -143,7 +143,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
</svg>
) : (
<svg
className="w-3.5 h-3.5"
className="h-3.5 w-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@@ -160,7 +160,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
</div>
</div>
{!isGrouped && (
<div className="hidden sm:flex w-8 h-8 bg-blue-600 rounded-full items-center justify-center text-white text-sm flex-shrink-0">
<div className="hidden h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-sm text-white sm:flex">
U
</div>
)}
@@ -169,7 +169,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
/* Compact task notification on the left */
<div className="w-full">
<div className="flex items-center gap-2 py-0.5">
<span className={`inline-block w-1.5 h-1.5 rounded-full flex-shrink-0 ${message.taskStatus === 'completed' ? 'bg-green-400 dark:bg-green-500' : 'bg-amber-400 dark:bg-amber-500'}`} />
<span className={`inline-block h-1.5 w-1.5 flex-shrink-0 rounded-full ${message.taskStatus === 'completed' ? 'bg-green-400 dark:bg-green-500' : 'bg-amber-400 dark:bg-amber-500'}`} />
<span className="text-xs text-gray-500 dark:text-gray-400">{message.content}</span>
</div>
</div>
@@ -177,18 +177,18 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
/* Claude/Error/Tool messages on the left */
<div className="w-full">
{!isGrouped && (
<div className="flex items-center space-x-3 mb-2">
<div className="mb-2 flex items-center space-x-3">
{message.type === 'error' ? (
<div className="w-8 h-8 bg-red-600 rounded-full flex items-center justify-center text-white text-sm flex-shrink-0">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-red-600 text-sm text-white">
!
</div>
) : message.type === 'tool' ? (
<div className="w-8 h-8 bg-gray-600 dark:bg-gray-700 rounded-full flex items-center justify-center text-white text-sm flex-shrink-0">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-600 text-sm text-white dark:bg-gray-700">
🔧
</div>
) : (
<div className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm flex-shrink-0 p-1">
<SessionProviderLogo provider={provider} className="w-full h-full" />
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full p-1 text-sm text-white">
<SessionProviderLogo provider={provider} className="h-full w-full" />
</div>
)}
<div className="text-sm font-medium text-gray-900 dark:text-white">
@@ -233,20 +233,20 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
// Error results - red error box with content
<div
id={`tool-result-${message.toolId}`}
className="relative mt-2 p-3 rounded border scroll-mt-4 bg-red-50/50 dark:bg-red-950/10 border-red-200/60 dark:border-red-800/40"
className="relative mt-2 scroll-mt-4 rounded border border-red-200/60 bg-red-50/50 p-3 dark:border-red-800/40 dark:bg-red-950/10"
>
<div className="relative flex items-center gap-1.5 mb-2">
<svg className="w-4 h-4 text-red-500 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="relative mb-2 flex items-center gap-1.5">
<svg className="h-4 w-4 text-red-500 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
<span className="text-xs font-medium text-red-700 dark:text-red-300">{t('messageTypes.error')}</span>
</div>
<div className="relative text-sm text-red-900 dark:text-red-100">
<Markdown className="prose prose-sm max-w-none prose-red dark:prose-invert">
<Markdown className="prose prose-sm prose-red max-w-none dark:prose-invert">
{String(message.toolResult.content || '')}
</Markdown>
{permissionSuggestion && (
<div className="mt-4 border-t border-red-200/60 dark:border-red-800/60 pt-3">
<div className="mt-4 border-t border-red-200/60 pt-3 dark:border-red-800/60">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
@@ -260,9 +260,9 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
}
}}
disabled={permissionSuggestion.isAllowed || permissionGrantState === 'granted'}
className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium border transition-colors ${permissionSuggestion.isAllowed || permissionGrantState === 'granted'
? 'bg-green-100 dark:bg-green-900/30 border-green-300/70 dark:border-green-800/60 text-green-800 dark:text-green-200 cursor-default'
: 'bg-white/80 dark:bg-gray-900/40 border-red-300/70 dark:border-red-800/60 text-red-700 dark:text-red-200 hover:bg-white dark:hover:bg-gray-900/70'
className={`inline-flex items-center gap-2 rounded-md border px-3 py-1.5 text-xs font-medium transition-colors ${permissionSuggestion.isAllowed || permissionGrantState === 'granted'
? 'cursor-default border-green-300/70 bg-green-100 text-green-800 dark:border-green-800/60 dark:bg-green-900/30 dark:text-green-200'
: 'border-red-300/70 bg-white/80 text-red-700 hover:bg-white dark:border-red-800/60 dark:bg-gray-900/40 dark:text-red-200 dark:hover:bg-gray-900/70'
}`}
>
{permissionSuggestion.isAllowed || permissionGrantState === 'granted'
@@ -273,7 +273,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
<button
type="button"
onClick={(e) => { e.stopPropagation(); onShowSettings(); }}
className="text-xs text-red-700 dark:text-red-200 underline hover:text-red-800 dark:hover:text-red-100"
className="text-xs text-red-700 underline hover:text-red-800 dark:text-red-200 dark:hover:text-red-100"
>
{t('permissions.openSettings')}
</button>
@@ -316,15 +316,15 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
</>
) : message.isInteractivePrompt ? (
// Special handling for interactive prompts
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4">
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20">
<div className="flex items-start gap-3">
<div className="w-8 h-8 bg-amber-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-amber-500">
<svg className="h-5 w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="flex-1">
<h4 className="font-semibold text-amber-900 dark:text-amber-100 text-base mb-3">
<h4 className="mb-3 text-base font-semibold text-amber-900 dark:text-amber-100">
{t('interactive.title')}
</h4>
{(() => {
@@ -348,29 +348,29 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
return (
<>
<p className="text-sm text-amber-800 dark:text-amber-200 mb-4">
<p className="mb-4 text-sm text-amber-800 dark:text-amber-200">
{questionLine}
</p>
{/* Option buttons */}
<div className="space-y-2 mb-4">
<div className="mb-4 space-y-2">
{options.map((option) => (
<button
key={option.number}
className={`w-full text-left px-4 py-3 rounded-lg border-2 transition-all ${option.isSelected
? 'bg-amber-600 dark:bg-amber-700 text-white border-amber-600 dark:border-amber-700 shadow-md'
: 'bg-white dark:bg-gray-800 text-amber-900 dark:text-amber-100 border-amber-300 dark:border-amber-700'
className={`w-full rounded-lg border-2 px-4 py-3 text-left transition-all ${option.isSelected
? 'border-amber-600 bg-amber-600 text-white shadow-md dark:border-amber-700 dark:bg-amber-700'
: 'border-amber-300 bg-white text-amber-900 dark:border-amber-700 dark:bg-gray-800 dark:text-amber-100'
} cursor-not-allowed opacity-75`}
disabled
>
<div className="flex items-center gap-3">
<span className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold ${option.isSelected
<span className={`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full text-sm font-bold ${option.isSelected
? 'bg-white/20'
: 'bg-amber-100 dark:bg-amber-800/50'
}`}>
{option.number}
</span>
<span className="text-sm sm:text-base font-medium flex-1">
<span className="flex-1 text-sm font-medium sm:text-base">
{option.text}
</span>
{option.isSelected && (
@@ -381,11 +381,11 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
))}
</div>
<div className="bg-amber-100 dark:bg-amber-800/30 rounded-lg p-3">
<p className="text-amber-900 dark:text-amber-100 text-sm font-medium mb-1">
<div className="rounded-lg bg-amber-100 p-3 dark:bg-amber-800/30">
<p className="mb-1 text-sm font-medium text-amber-900 dark:text-amber-100">
{t('interactive.waiting')}
</p>
<p className="text-amber-800 dark:text-amber-200 text-xs">
<p className="text-xs text-amber-800 dark:text-amber-200">
{t('interactive.instruction')}
</p>
</div>
@@ -399,14 +399,14 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
/* Thinking messages - collapsible by default */
<div className="text-sm text-gray-700 dark:text-gray-300">
<details className="group">
<summary className="cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 font-medium flex items-center gap-2">
<svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<summary className="flex cursor-pointer items-center gap-2 font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg className="h-3 w-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
<span>{t('thinking.emoji')}</span>
</summary>
<div className="mt-2 pl-4 border-l-2 border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 text-sm">
<Markdown className="prose prose-sm max-w-none dark:prose-invert prose-gray">
<div className="mt-2 border-l-2 border-gray-300 pl-4 text-sm text-gray-600 dark:border-gray-600 dark:text-gray-400">
<Markdown className="prose prose-sm prose-gray max-w-none dark:prose-invert">
{message.content}
</Markdown>
</div>
@@ -417,10 +417,10 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
{/* Thinking accordion for reasoning */}
{showThinking && message.reasoning && (
<details className="mb-3">
<summary className="cursor-pointer text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 font-medium">
<summary className="cursor-pointer font-medium text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200">
{t('thinking.emoji')}
</summary>
<div className="mt-2 pl-4 border-l-2 border-gray-300 dark:border-gray-600 italic text-gray-600 dark:text-gray-400 text-sm">
<div className="mt-2 border-l-2 border-gray-300 pl-4 text-sm italic text-gray-600 dark:border-gray-600 dark:text-gray-400">
<div className="whitespace-pre-wrap">
{message.reasoning}
</div>
@@ -441,15 +441,15 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
return (
<div className="my-2">
<div className="flex items-center gap-2 mb-2 text-sm text-gray-600 dark:text-gray-400">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="mb-2 flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
<span className="font-medium">{t('json.response')}</span>
</div>
<div className="bg-gray-800 dark:bg-gray-900 border border-gray-600/30 dark:border-gray-700 rounded-lg overflow-hidden">
<pre className="p-4 overflow-x-auto">
<code className="text-gray-100 dark:text-gray-200 text-sm font-mono block whitespace-pre">
<div className="overflow-hidden rounded-lg border border-gray-600/30 bg-gray-800 dark:border-gray-700 dark:bg-gray-900">
<pre className="overflow-x-auto p-4">
<code className="block whitespace-pre font-mono text-sm text-gray-100 dark:text-gray-200">
{formatted}
</code>
</pre>
@@ -463,7 +463,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
// Normal rendering for non-JSON content
return message.type === 'assistant' ? (
<Markdown className="prose prose-sm max-w-none dark:prose-invert prose-gray">
<Markdown className="prose prose-sm prose-gray max-w-none dark:prose-invert">
{content}
</Markdown>
) : (
@@ -476,7 +476,7 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, o
)}
{!isGrouped && (
<div className="text-[11px] text-gray-400 dark:text-gray-500 mt-1">
<div className="mt-1 text-[11px] text-gray-400 dark:text-gray-500">
{formattedTime}
</div>
)}

View File

@@ -56,7 +56,7 @@ export default function PermissionRequestsBanner({
return (
<div
key={request.requestId}
className="rounded-lg border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/20 p-3 shadow-sm"
className="rounded-lg border border-amber-200 bg-amber-50 p-3 shadow-sm dark:border-amber-800 dark:bg-amber-900/20"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
@@ -74,10 +74,10 @@ export default function PermissionRequestsBanner({
{rawInput && (
<details className="mt-2">
<summary className="cursor-pointer text-xs text-amber-800 dark:text-amber-200 hover:text-amber-900 dark:hover:text-amber-100">
<summary className="cursor-pointer text-xs text-amber-800 hover:text-amber-900 dark:text-amber-200 dark:hover:text-amber-100">
View tool input
</summary>
<pre className="mt-2 max-h-40 overflow-auto rounded-md bg-white/80 dark:bg-gray-900/60 border border-amber-200/60 dark:border-amber-800/60 p-2 text-xs text-amber-900 dark:text-amber-100 whitespace-pre-wrap">
<pre className="mt-2 max-h-40 overflow-auto whitespace-pre-wrap rounded-md border border-amber-200/60 bg-white/80 p-2 text-xs text-amber-900 dark:border-amber-800/60 dark:bg-gray-900/60 dark:text-amber-100">
{rawInput}
</pre>
</details>
@@ -87,7 +87,7 @@ export default function PermissionRequestsBanner({
<button
type="button"
onClick={() => handlePermissionDecision(request.requestId, { allow: true })}
className="inline-flex items-center gap-2 rounded-md bg-amber-600 text-white text-xs font-medium px-3 py-1.5 hover:bg-amber-700 transition-colors"
className="inline-flex items-center gap-2 rounded-md bg-amber-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-amber-700"
>
Allow once
</button>
@@ -99,10 +99,10 @@ export default function PermissionRequestsBanner({
}
handlePermissionDecision(matchingRequestIds, { allow: true, rememberEntry: permissionEntry });
}}
className={`inline-flex items-center gap-2 rounded-md text-xs font-medium px-3 py-1.5 border transition-colors ${
className={`inline-flex items-center gap-2 rounded-md border px-3 py-1.5 text-xs font-medium transition-colors ${
permissionEntry
? 'border-amber-300 text-amber-800 hover:bg-amber-100 dark:border-amber-700 dark:text-amber-100 dark:hover:bg-amber-900/30'
: 'border-gray-300 text-gray-400 cursor-not-allowed'
: 'cursor-not-allowed border-gray-300 text-gray-400'
}`}
disabled={!permissionEntry}
>
@@ -111,7 +111,7 @@ export default function PermissionRequestsBanner({
<button
type="button"
onClick={() => handlePermissionDecision(request.requestId, { allow: false, message: 'User denied tool use' })}
className="inline-flex items-center gap-2 rounded-md text-xs font-medium px-3 py-1.5 border border-red-300 text-red-700 hover:bg-red-50 dark:border-red-800 dark:text-red-200 dark:hover:bg-red-900/30 transition-colors"
className="inline-flex items-center gap-2 rounded-md border border-red-300 px-3 py-1.5 text-xs font-medium text-red-700 transition-colors hover:bg-red-50 dark:border-red-800 dark:text-red-200 dark:hover:bg-red-900/30"
>
Deny
</button>

View File

@@ -125,20 +125,20 @@ export default function ProviderSelectionEmptyState({
/* ── New session — provider picker ── */
if (!selectedSession && !currentSessionId) {
return (
<div className="flex items-center justify-center h-full px-4">
<div className="flex h-full items-center justify-center px-4">
<div className="w-full max-w-md">
{/* Heading */}
<div className="text-center mb-8">
<h2 className="text-lg sm:text-xl font-semibold text-foreground tracking-tight">
<div className="mb-8 text-center">
<h2 className="text-lg font-semibold tracking-tight text-foreground sm:text-xl">
{t('providerSelection.title')}
</h2>
<p className="text-[13px] text-muted-foreground mt-1">
<p className="mt-1 text-[13px] text-muted-foreground">
{t('providerSelection.description')}
</p>
</div>
{/* Provider cards — horizontal row, equal width */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-2.5 mb-6">
<div className="mb-6 grid grid-cols-2 gap-2 sm:grid-cols-4 sm:gap-2.5">
{PROVIDERS.map((p) => {
const active = provider === p.id;
return (
@@ -146,27 +146,27 @@ export default function ProviderSelectionEmptyState({
key={p.id}
onClick={() => selectProvider(p.id)}
className={`
relative flex flex-col items-center gap-2.5 pt-5 pb-4 px-2
rounded-xl border-[1.5px] transition-all duration-150
relative flex flex-col items-center gap-2.5 rounded-xl border-[1.5px] px-2
pb-4 pt-5 transition-all duration-150
active:scale-[0.97]
${active
? `${p.accent} ${p.ring} ring-2 bg-card shadow-sm`
: 'border-border bg-card/60 hover:bg-card hover:border-border/80'
? `${p.accent} ${p.ring} bg-card shadow-sm ring-2`
: 'border-border bg-card/60 hover:border-border/80 hover:bg-card'
}
`}
>
<SessionProviderLogo
provider={p.id}
className={`w-9 h-9 transition-transform duration-150 ${active ? 'scale-110' : ''}`}
className={`h-9 w-9 transition-transform duration-150 ${active ? 'scale-110' : ''}`}
/>
<div className="text-center">
<p className="text-[13px] font-semibold text-foreground leading-none">{p.name}</p>
<p className="text-[10px] text-muted-foreground mt-1 leading-tight">{t(p.infoKey)}</p>
<p className="text-[13px] font-semibold leading-none text-foreground">{p.name}</p>
<p className="mt-1 text-[10px] leading-tight text-muted-foreground">{t(p.infoKey)}</p>
</div>
{/* Check badge */}
{active && (
<div className={`absolute -top-1 -right-1 w-[18px] h-[18px] rounded-full ${p.check} flex items-center justify-center shadow-sm`}>
<Check className="w-2.5 h-2.5" strokeWidth={3} />
<div className={`absolute -right-1 -top-1 h-[18px] w-[18px] rounded-full ${p.check} flex items-center justify-center shadow-sm`}>
<Check className="h-2.5 w-2.5" strokeWidth={3} />
</div>
)}
</button>
@@ -175,21 +175,21 @@ export default function ProviderSelectionEmptyState({
</div>
{/* Model picker — appears after provider is chosen */}
<div className={`transition-all duration-200 ${provider ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-1 pointer-events-none'}`}>
<div className="flex items-center justify-center gap-2 mb-5">
<div className={`transition-all duration-200 ${provider ? 'translate-y-0 opacity-100' : 'pointer-events-none translate-y-1 opacity-0'}`}>
<div className="mb-5 flex items-center justify-center gap-2">
<span className="text-sm text-muted-foreground">{t('providerSelection.selectModel')}</span>
<div className="relative">
<select
value={currentModel}
onChange={(e) => handleModelChange(e.target.value)}
tabIndex={-1}
className="appearance-none pl-3 pr-7 py-1.5 text-sm font-medium bg-muted/50 border border-border/60 rounded-lg text-foreground cursor-pointer hover:bg-muted transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20"
className="cursor-pointer appearance-none rounded-lg border border-border/60 bg-muted/50 py-1.5 pl-3 pr-7 text-sm font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus:ring-2 focus:ring-primary/20"
>
{modelConfig.OPTIONS.map(({ value, label }: { value: string; label: string }) => (
<option key={value} value={value}>{label}</option>
))}
</select>
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 w-3 h-3 text-muted-foreground pointer-events-none" />
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
</div>
</div>
@@ -219,10 +219,10 @@ export default function ProviderSelectionEmptyState({
/* ── Existing session — continue prompt ── */
if (selectedSession) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-center px-6 max-w-md">
<p className="text-lg font-semibold text-foreground mb-1.5">{t('session.continue.title')}</p>
<p className="text-sm text-muted-foreground leading-relaxed">{t('session.continue.description')}</p>
<div className="flex h-full items-center justify-center">
<div className="max-w-md px-6 text-center">
<p className="mb-1.5 text-lg font-semibold text-foreground">{t('session.continue.title')}</p>
<p className="text-sm leading-relaxed text-muted-foreground">{t('session.continue.description')}</p>
{tasksEnabled && isTaskMasterInstalled && (
<div className="mt-5">

View File

@@ -1,7 +1,6 @@
import { useState, useRef, useEffect } from 'react';
import { Brain, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { thinkingModes } from '../../constants/thinkingModes';
type ThinkingModeSelectorProps = {
@@ -53,18 +52,18 @@ function ThinkingModeSelector({ selectedMode, onModeChange, onClose, className =
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className={`w-10 h-10 sm:w-10 sm:h-10 rounded-full flex items-center justify-center transition-all duration-200 ${selectedMode === 'none'
className={`flex h-10 w-10 items-center justify-center rounded-full transition-all duration-200 sm:h-10 sm:w-10 ${selectedMode === 'none'
? 'bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600'
: 'bg-blue-100 hover:bg-blue-200 dark:bg-blue-900 dark:hover:bg-blue-800'
}`}
title={t('thinkingMode.buttonTitle', { mode: currentMode.name })}
>
<IconComponent className={`w-5 h-5 ${currentMode.color}`} />
<IconComponent className={`h-5 w-5 ${currentMode.color}`} />
</button>
{isOpen && (
<div className="absolute bottom-full right-0 mb-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="p-3 border-b border-gray-200 dark:border-gray-700">
<div className="absolute bottom-full right-0 mb-2 w-64 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800">
<div className="border-b border-gray-200 p-3 dark:border-gray-700">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">
{t('thinkingMode.selector.title')}
@@ -74,12 +73,12 @@ function ThinkingModeSelector({ selectedMode, onModeChange, onClose, className =
setIsOpen(false);
if (onClose) onClose();
}}
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
className="rounded p-1 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<X className="w-4 h-4 text-gray-500" />
<X className="h-4 w-4 text-gray-500" />
</button>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{t('thinkingMode.selector.description')}
</p>
</div>
@@ -97,30 +96,30 @@ function ThinkingModeSelector({ selectedMode, onModeChange, onClose, className =
setIsOpen(false);
if (onClose) onClose();
}}
className={`w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors ${isSelected ? 'bg-gray-50 dark:bg-gray-700' : ''
className={`w-full px-4 py-3 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-700 ${isSelected ? 'bg-gray-50 dark:bg-gray-700' : ''
}`}
>
<div className="flex items-start gap-3">
<div className={`mt-0.5 ${mode.icon ? mode.color : 'text-gray-400'}`}>
{ModeIcon ? <ModeIcon className="w-5 h-5" /> : <div className="w-5 h-5" />}
{ModeIcon ? <ModeIcon className="h-5 w-5" /> : <div className="h-5 w-5" />}
</div>
<div className="flex-1 min-w-0">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={`font-medium text-sm ${isSelected ? 'text-gray-900 dark:text-white' : 'text-gray-700 dark:text-gray-300'
<span className={`text-sm font-medium ${isSelected ? 'text-gray-900 dark:text-white' : 'text-gray-700 dark:text-gray-300'
}`}>
{mode.name}
</span>
{isSelected && (
<span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-2 py-0.5 rounded">
<span className="rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-700 dark:bg-blue-900 dark:text-blue-300">
{t('thinkingMode.selector.active')}
</span>
)}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
<p className="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{mode.description}
</p>
{mode.prefix && (
<code className="text-xs bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded mt-1 inline-block">
<code className="mt-1 inline-block rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-700">
{mode.prefix}
</code>
)}
@@ -131,7 +130,7 @@ function ThinkingModeSelector({ selectedMode, onModeChange, onClose, className =
})}
</div>
<div className="p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<div className="border-t border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-900">
<p className="text-xs text-gray-600 dark:text-gray-400">
<strong>Tip:</strong> {t('thinkingMode.selector.tip')}
</p>

View File

@@ -22,7 +22,7 @@ export default function TokenUsagePie({ used, total }: TokenUsagePieProps) {
return (
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
<svg width="24" height="24" viewBox="0 0 24 24" className="transform -rotate-90">
<svg width="24" height="24" viewBox="0 0 24 24" className="-rotate-90 transform">
{/* Background circle */}
<circle
cx="12"

View File

@@ -220,7 +220,7 @@ export default function CodeEditor({
/>
{saveError && (
<div className="px-3 py-1.5 text-xs text-red-700 bg-red-50 border-b border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-900/40">
<div className="border-b border-red-200 bg-red-50 px-3 py-1.5 text-xs text-red-700 dark:border-red-900/40 dark:bg-red-900/20 dark:text-red-300">
{saveError}
</div>
)}

View File

@@ -102,20 +102,20 @@ export default function EditorSidebar({
const useFlexLayout = editorExpanded || (fillSpace && !hasManualWidth);
return (
<div ref={containerRef} className={`flex h-full flex-shrink-0 min-w-0 ${editorExpanded ? 'flex-1' : ''}`}>
<div ref={containerRef} className={`flex h-full min-w-0 flex-shrink-0 ${editorExpanded ? 'flex-1' : ''}`}>
{!editorExpanded && (
<div
ref={resizeHandleRef}
onMouseDown={onResizeStart}
className="flex-shrink-0 w-1 bg-gray-200 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 cursor-col-resize transition-colors relative group"
className="group relative w-1 flex-shrink-0 cursor-col-resize bg-gray-200 transition-colors hover:bg-blue-500 dark:bg-gray-700 dark:hover:bg-blue-600"
title="Drag to resize"
>
<div className="absolute inset-y-0 left-1/2 -translate-x-1/2 w-1 bg-blue-500 dark:bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="absolute inset-y-0 left-1/2 w-1 -translate-x-1/2 bg-blue-500 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-blue-600" />
</div>
)}
<div
className={`border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden ${useFlexLayout ? 'flex-1 min-w-0' : `flex-shrink-0 min-w-[${MIN_EDITOR_WIDTH}px]`}`}
className={`h-full overflow-hidden border-l border-gray-200 dark:border-gray-700 ${useFlexLayout ? 'min-w-0 flex-1' : `min-w-[ flex-shrink-0${MIN_EDITOR_WIDTH}px]`}`}
style={useFlexLayout ? undefined : { width: `${effectiveWidth}px`, minWidth: `${MIN_EDITOR_WIDTH}px` }}
>
<CodeEditor

View File

@@ -20,20 +20,20 @@ export default function CodeEditorBinaryFile({
message,
}: CodeEditorBinaryFileProps) {
const binaryContent = (
<div className="w-full h-full flex flex-col items-center justify-center bg-background text-muted-foreground p-8">
<div className="flex flex-col items-center gap-4 max-w-md text-center">
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center">
<svg className="w-8 h-8 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div className="flex h-full w-full flex-col items-center justify-center bg-background p-8 text-muted-foreground">
<div className="flex max-w-md flex-col items-center gap-4 text-center">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-muted">
<svg className="h-8 w-8 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<h3 className="text-lg font-medium text-foreground mb-2">{title}</h3>
<h3 className="mb-2 text-lg font-medium text-foreground">{title}</h3>
<p className="text-sm text-muted-foreground">{message}</p>
</div>
<button
onClick={onClose}
className="mt-4 px-4 py-2 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
className="mt-4 rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground transition-colors hover:bg-primary/90"
>
Close
</button>
@@ -43,18 +43,18 @@ export default function CodeEditorBinaryFile({
if (isSidebar) {
return (
<div className="w-full h-full flex flex-col bg-background">
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border flex-shrink-0">
<div className="flex items-center gap-2 min-w-0 flex-1">
<h3 className="text-sm font-medium text-gray-900 dark:text-white truncate">{file.name}</h3>
<div className="flex h-full w-full flex-col bg-background">
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-3 py-1.5">
<div className="flex min-w-0 flex-1 items-center gap-2">
<h3 className="truncate text-sm font-medium text-gray-900 dark:text-white">{file.name}</h3>
</div>
<button
type="button"
onClick={onClose}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title="Close"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
@@ -75,23 +75,23 @@ export default function CodeEditorBinaryFile({
return (
<div className={containerClassName}>
<div className={innerClassName}>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border flex-shrink-0">
<div className="flex items-center gap-2 min-w-0 flex-1">
<h3 className="text-sm font-medium text-gray-900 dark:text-white truncate">{file.name}</h3>
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-3 py-1.5">
<div className="flex min-w-0 flex-1 items-center gap-2">
<h3 className="truncate text-sm font-medium text-gray-900 dark:text-white">{file.name}</h3>
</div>
<div className="flex items-center gap-0.5 shrink-0">
<div className="flex shrink-0 items-center gap-0.5">
<button
type="button"
onClick={onToggleFullscreen}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
>
{isFullscreen ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 9V4.5M9 9H4.5M9 9L3.5 3.5M9 15v4.5M9 15H4.5M9 15l-5.5 5.5M15 9h4.5M15 9V4.5M15 9l5.5-5.5M15 15h4.5M15 15v4.5m0-4.5l5.5 5.5" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
</svg>
)}
@@ -99,10 +99,10 @@ export default function CodeEditorBinaryFile({
<button
type="button"
onClick={onClose}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title="Close"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>

View File

@@ -12,7 +12,7 @@ export default function CodeEditorFooter({
shortcutsLabel,
}: CodeEditorFooterProps) {
return (
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border bg-muted flex-shrink-0">
<div className="flex flex-shrink-0 items-center justify-between border-t border-border bg-muted px-3 py-1.5">
<div className="flex items-center gap-3 text-xs text-gray-600 dark:text-gray-400">
<span>
{linesLabel} {content.split('\n').length}

View File

@@ -49,74 +49,74 @@ export default function CodeEditorHeader({
const saveTitle = saveSuccess ? labels.saved : saving ? labels.saving : labels.save;
return (
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border flex-shrink-0 min-w-0 gap-2">
<div className="flex min-w-0 flex-shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-1.5">
{/* File info - can shrink */}
<div className="flex items-center gap-2 min-w-0 flex-1 shrink">
<div className="flex min-w-0 flex-1 shrink items-center gap-2">
<div className="min-w-0 shrink">
<div className="flex items-center gap-2 min-w-0">
<h3 className="text-sm font-medium text-gray-900 dark:text-white truncate">{file.name}</h3>
<div className="flex min-w-0 items-center gap-2">
<h3 className="truncate text-sm font-medium text-gray-900 dark:text-white">{file.name}</h3>
{file.diffInfo && (
<span className="text-[10px] bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 px-1.5 py-0.5 rounded whitespace-nowrap shrink-0">
<span className="shrink-0 whitespace-nowrap rounded bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-600 dark:bg-blue-900 dark:text-blue-300">
{labels.showingChanges}
</span>
)}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">{file.path}</p>
<p className="truncate text-xs text-gray-500 dark:text-gray-400">{file.path}</p>
</div>
</div>
{/* Buttons - don't shrink, always visible */}
<div className="flex items-center gap-0.5 shrink-0">
<div className="flex shrink-0 items-center gap-0.5">
{isMarkdownFile && (
<button
type="button"
onClick={onToggleMarkdownPreview}
className={`p-1.5 rounded-md flex items-center justify-center transition-colors ${
className={`flex items-center justify-center rounded-md p-1.5 transition-colors ${
markdownPreview
? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white'
}`}
title={markdownPreview ? labels.editMarkdown : labels.previewMarkdown}
>
{markdownPreview ? <Code2 className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
{markdownPreview ? <Code2 className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
)}
<button
type="button"
onClick={onOpenSettings}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title={labels.settings}
>
<SettingsIcon className="w-4 h-4" />
<SettingsIcon className="h-4 w-4" />
</button>
<button
type="button"
onClick={onDownload}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title={labels.download}
>
<Download className="w-4 h-4" />
<Download className="h-4 w-4" />
</button>
<button
type="button"
onClick={onSave}
disabled={saving}
className={`p-1.5 rounded-md disabled:opacity-50 flex items-center justify-center transition-colors ${
className={`flex items-center justify-center rounded-md p-1.5 transition-colors disabled:opacity-50 ${
saveSuccess
? 'text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-900/30'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white'
}`}
title={saveTitle}
>
{saveSuccess ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
<Save className="w-4 h-4" />
<Save className="h-4 w-4" />
)}
</button>
@@ -124,20 +124,20 @@ export default function CodeEditorHeader({
<button
type="button"
onClick={onToggleFullscreen}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title={isFullscreen ? labels.exitFullscreen : labels.fullscreen}
>
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
{isFullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
</button>
)}
<button
type="button"
onClick={onClose}
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center"
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
title={labels.close}
>
<X className="w-4 h-4" />
<X className="h-4 w-4" />
</button>
</div>
</div>

View File

@@ -15,17 +15,17 @@ export default function CodeEditorLoadingState({
<>
<style>{getEditorLoadingStyles(isDarkMode)}</style>
{isSidebar ? (
<div className="w-full h-full flex items-center justify-center bg-background">
<div className="flex h-full w-full items-center justify-center bg-background">
<div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" />
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-blue-600" />
<span className="text-gray-900 dark:text-white">{loadingText}</span>
</div>
</div>
) : (
<div className="fixed inset-0 z-[9999] md:bg-black/50 md:flex md:items-center md:justify-center">
<div className="code-editor-loading w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center">
<div className="fixed inset-0 z-[9999] md:flex md:items-center md:justify-center md:bg-black/50">
<div className="code-editor-loading flex h-full w-full items-center justify-center p-8 md:h-auto md:w-auto md:rounded-lg">
<div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" />
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-blue-600" />
<span className="text-gray-900 dark:text-white">{loadingText}</span>
</div>
</div>

View File

@@ -27,7 +27,7 @@ export default function CodeEditorSurface({
if (markdownPreview && isMarkdownFile) {
return (
<div className="h-full overflow-y-auto bg-white dark:bg-gray-900">
<div className="max-w-4xl mx-auto px-8 py-6 prose prose-sm dark:prose-invert prose-headings:font-semibold prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-code:text-sm prose-pre:bg-gray-900 prose-img:rounded-lg max-w-none">
<div className="prose prose-sm mx-auto max-w-4xl max-w-none px-8 py-6 dark:prose-invert prose-headings:font-semibold prose-a:text-blue-600 prose-code:text-sm prose-pre:bg-gray-900 prose-img:rounded-lg dark:prose-a:text-blue-400">
<MarkdownPreview content={content} />
</div>
</div>

View File

@@ -24,7 +24,7 @@ export default function MarkdownCodeBlock({
if (shouldRenderInline) {
return (
<code
className={`font-mono text-[0.9em] px-1.5 py-0.5 rounded-md bg-gray-100 text-gray-900 border border-gray-200 dark:bg-gray-800/60 dark:text-gray-100 dark:border-gray-700 whitespace-pre-wrap break-words ${className || ''}`}
className={`whitespace-pre-wrap break-words rounded-md border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[0.9em] text-gray-900 dark:border-gray-700 dark:bg-gray-800/60 dark:text-gray-100 ${className || ''}`}
{...props}
>
{children}
@@ -36,9 +36,9 @@ export default function MarkdownCodeBlock({
const language = languageMatch ? languageMatch[1] : 'text';
return (
<div className="relative group my-2">
<div className="group relative my-2">
{language !== 'text' && (
<div className="absolute top-2 left-3 z-10 text-xs text-gray-400 font-medium uppercase">{language}</div>
<div className="absolute left-3 top-2 z-10 text-xs font-medium uppercase text-gray-400">{language}</div>
)}
<button
@@ -50,7 +50,7 @@ export default function MarkdownCodeBlock({
setTimeout(() => setCopied(false), 2000);
}
})}
className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity text-xs px-2 py-1 rounded-md bg-gray-700/80 hover:bg-gray-700 text-white border border-gray-600"
className="absolute right-2 top-2 z-10 rounded-md border border-gray-600 bg-gray-700/80 px-2 py-1 text-xs text-white opacity-0 transition-opacity hover:bg-gray-700 group-hover:opacity-100"
>
{copied ? 'Copied!' : 'Copy'}
</button>

View File

@@ -13,26 +13,26 @@ type MarkdownPreviewProps = {
const markdownPreviewComponents: Components = {
code: MarkdownCodeBlock,
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-gray-300 dark:border-gray-600 pl-4 italic text-gray-600 dark:text-gray-400 my-2">
<blockquote className="my-2 border-l-4 border-gray-300 pl-4 italic text-gray-600 dark:border-gray-600 dark:text-gray-400">
{children}
</blockquote>
),
a: ({ href, children }) => (
<a href={href} className="text-blue-600 dark:text-blue-400 hover:underline" target="_blank" rel="noopener noreferrer">
<a href={href} className="text-blue-600 hover:underline dark:text-blue-400" target="_blank" rel="noopener noreferrer">
{children}
</a>
),
table: ({ children }) => (
<div className="overflow-x-auto my-2">
<div className="my-2 overflow-x-auto">
<table className="min-w-full border-collapse border border-gray-200 dark:border-gray-700">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-gray-50 dark:bg-gray-800">{children}</thead>,
th: ({ children }) => (
<th className="px-3 py-2 text-left text-sm font-semibold border border-gray-200 dark:border-gray-700">{children}</th>
<th className="border border-gray-200 px-3 py-2 text-left text-sm font-semibold dark:border-gray-700">{children}</th>
),
td: ({ children }) => (
<td className="px-3 py-2 align-top text-sm border border-gray-200 dark:border-gray-700">{children}</td>
<td className="border border-gray-200 px-3 py-2 align-top text-sm dark:border-gray-700">{children}</td>
),
};

View File

@@ -275,13 +275,13 @@ export default function FileContextMenu({
>
{isLoading ? (
<div className="flex items-center justify-center py-4">
<RefreshCw className="w-4 h-4 animate-spin text-muted-foreground" />
<RefreshCw className="h-4 w-4 animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">{t('fileTree.context.loading', 'Loading...')}</span>
</div>
) : (
menuActions.map((action) => (
<Fragment key={action.key}>
{action.showDividerBefore && <div className="h-px bg-border my-1 mx-2" />}
{action.showDividerBefore && <div className="mx-2 my-1 h-px bg-border" />}
<button
role="menuitem"
tabIndex={action.isDisabled ? -1 : 0}
@@ -298,9 +298,9 @@ export default function FileContextMenu({
isLoading && 'pointer-events-none',
)}
>
{action.icon && <action.icon className="w-4 h-4 flex-shrink-0" />}
{action.icon && <action.icon className="h-4 w-4 flex-shrink-0" />}
<span className="flex-1">{action.label}</span>
{action.shortcut && <span className="text-xs text-muted-foreground font-mono">{action.shortcut}</span>}
{action.shortcut && <span className="font-mono text-xs text-muted-foreground">{action.shortcut}</span>}
</button>
</Fragment>
))

View File

@@ -2,7 +2,6 @@ import { useCallback, useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTriangle, Check, X, Loader2, Folder, Upload } from 'lucide-react';
import { cn } from '../../../lib/utils';
import ImageViewer from './ImageViewer';
import { ICON_SIZE_CLASS, getFileIconData } from '../constants/fileIcons';
import { useExpandedDirectories } from '../hooks/useExpandedDirectories';
import { useFileTreeData } from '../hooks/useFileTreeData';
@@ -12,12 +11,13 @@ import { useFileTreeViewMode } from '../hooks/useFileTreeViewMode';
import { useFileTreeUpload } from '../hooks/useFileTreeUpload';
import type { FileTreeImageSelection, FileTreeNode } from '../types/types';
import { formatFileSize, formatRelativeTime, isImageFile } from '../utils/fileTreeUtils';
import { Project } from '../../../types/app';
import { ScrollArea, Input } from '../../../shared/view/ui';
import FileTreeBody from './FileTreeBody';
import FileTreeDetailedColumns from './FileTreeDetailedColumns';
import FileTreeHeader from './FileTreeHeader';
import FileTreeLoadingState from './FileTreeLoadingState';
import { Project } from '../../../types/app';
import { ScrollArea, Input } from '../../../shared/view/ui';
import ImageViewer from './ImageViewer';
type FileTreeProps = {
@@ -123,7 +123,7 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
return (
<div
ref={upload.treeRef}
className="h-full flex flex-col bg-background relative"
className="relative flex h-full flex-col bg-background"
onDragEnter={upload.handleDragEnter}
onDragOver={upload.handleDragOver}
onDragLeave={upload.handleDragLeave}
@@ -131,9 +131,9 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
>
{/* Drag overlay */}
{upload.isDragOver && (
<div className="absolute inset-0 z-50 bg-blue-500/10 border-2 border-dashed border-blue-500 flex items-center justify-center">
<div className="bg-background/95 px-6 py-4 rounded-lg shadow-lg flex items-center gap-3">
<Upload className="w-6 h-6 text-blue-500" />
<div className="absolute inset-0 z-50 flex items-center justify-center border-2 border-dashed border-blue-500 bg-blue-500/10">
<div className="flex items-center gap-3 rounded-lg bg-background/95 px-6 py-4 shadow-lg">
<Upload className="h-6 w-6 text-blue-500" />
<span className="text-sm font-medium">{t('fileTree.dropToUpload', 'Drop files to upload')}</span>
</div>
</div>
@@ -158,7 +158,7 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
{/* New item input */}
{operations.isCreating && (
<div
className="flex items-center gap-1.5 py-[3px] pr-2 mb-1"
className="mb-1 flex items-center gap-1.5 py-[3px] pr-2"
style={{ paddingLeft: `${(operations.newItemParent.split('/').length - 1) * 16 + 4}px` }}
>
{operations.newItemType === 'directory' ? (
@@ -181,7 +181,7 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
if (operations.isCreating) operations.handleConfirmCreate();
}, 100);
}}
className="h-6 text-sm flex-1"
className="h-6 flex-1 text-sm"
disabled={operations.operationLoading}
/>
</div>
@@ -225,10 +225,10 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
{/* Delete Confirmation Dialog */}
{operations.deleteConfirmation.isOpen && operations.deleteConfirmation.item && (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50">
<div className="bg-background border border-border rounded-lg shadow-lg p-4 max-w-sm mx-4">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-full bg-red-100 dark:bg-red-900/30">
<AlertTriangle className="w-5 h-5 text-red-600 dark:text-red-400" />
<div className="mx-4 max-w-sm rounded-lg border border-border bg-background p-4 shadow-lg">
<div className="mb-4 flex items-center gap-3">
<div className="rounded-full bg-red-100 p-2 dark:bg-red-900/30">
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400" />
</div>
<div>
<h3 className="font-medium text-foreground">
@@ -241,7 +241,7 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
</p>
</div>
</div>
<p className="text-sm text-muted-foreground mb-4">
<p className="mb-4 text-sm text-muted-foreground">
{operations.deleteConfirmation.item.type === 'directory'
? t('fileTree.delete.folderWarning', 'This folder and all its contents will be permanently deleted.')
: t('fileTree.delete.fileWarning', 'This file will be permanently deleted.')}
@@ -250,16 +250,16 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
<button
onClick={operations.handleCancelDelete}
disabled={operations.operationLoading}
className="px-3 py-1.5 text-sm rounded-md hover:bg-accent transition-colors"
className="rounded-md px-3 py-1.5 text-sm transition-colors hover:bg-accent"
>
{t('common.cancel', 'Cancel')}
</button>
<button
onClick={operations.handleConfirmDelete}
disabled={operations.operationLoading}
className="px-3 py-1.5 text-sm rounded-md bg-red-600 text-white hover:bg-red-700 transition-colors disabled:opacity-50 flex items-center gap-2"
className="flex items-center gap-2 rounded-md bg-red-600 px-3 py-1.5 text-sm text-white transition-colors hover:bg-red-700 disabled:opacity-50"
>
{operations.operationLoading && <Loader2 className="w-4 h-4 animate-spin" />}
{operations.operationLoading && <Loader2 className="h-4 w-4 animate-spin" />}
{t('fileTree.delete.confirm', 'Delete')}
</button>
</div>
@@ -278,9 +278,9 @@ export default function FileTree({ selectedProject, onFileOpen }: FileTreeProps)
)}
>
{toast.type === 'success' ? (
<Check className="w-4 h-4" />
<Check className="h-4 w-4" />
) : (
<X className="w-4 h-4" />
<X className="h-4 w-4" />
)}
<span className="text-sm">{toast.message}</span>
</div>

View File

@@ -4,7 +4,7 @@ export default function FileTreeDetailedColumns() {
const { t } = useTranslation();
return (
<div className="px-3 pt-1.5 pb-1 border-b border-border">
<div className="border-b border-border px-3 pb-1 pt-1.5">
<div className="grid grid-cols-12 gap-2 px-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
<div className="col-span-5">{t('fileTree.name')}</div>
<div className="col-span-2">{t('fileTree.size')}</div>

View File

@@ -8,11 +8,11 @@ type FileTreeEmptyStateProps = {
export default function FileTreeEmptyState({ icon: Icon, title, description }: FileTreeEmptyStateProps) {
return (
<div className="text-center py-8">
<div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center mx-auto mb-3">
<Icon className="w-6 h-6 text-muted-foreground" />
<div className="py-8 text-center">
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-lg bg-muted">
<Icon className="h-6 w-6 text-muted-foreground" />
</div>
<h4 className="font-medium text-foreground mb-1">{title}</h4>
<h4 className="mb-1 font-medium text-foreground">{title}</h4>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
);

View File

@@ -34,7 +34,7 @@ export default function FileTreeHeader({
const { t } = useTranslation();
return (
<div className="px-3 pt-3 pb-2 border-b border-border space-y-2">
<div className="space-y-2 border-b border-border px-3 pb-2 pt-3">
{/* Title and Toolbar */}
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium text-foreground">{t('fileTree.files')}</h3>
@@ -50,7 +50,7 @@ export default function FileTreeHeader({
aria-label={t('fileTree.newFile', 'New File (Cmd+N)')}
disabled={operationLoading}
>
<FileText className="w-3.5 h-3.5" />
<FileText className="h-3.5 w-3.5" />
</Button>
)}
{onNewFolder && (
@@ -63,7 +63,7 @@ export default function FileTreeHeader({
aria-label={t('fileTree.newFolder', 'New Folder (Cmd+Shift+N)')}
disabled={operationLoading}
>
<FolderPlus className="w-3.5 h-3.5" />
<FolderPlus className="h-3.5 w-3.5" />
</Button>
)}
{onRefresh && (
@@ -88,11 +88,11 @@ export default function FileTreeHeader({
title={t('fileTree.collapseAll', 'Collapse All')}
aria-label={t('fileTree.collapseAll', 'Collapse All')}
>
<ChevronDown className="w-3.5 h-3.5" />
<ChevronDown className="h-3.5 w-3.5" />
</Button>
)}
{/* Divider */}
<div className="w-px h-4 bg-border mx-0.5" />
<div className="mx-0.5 h-4 w-px bg-border" />
{/* View mode buttons */}
<Button
variant={viewMode === 'simple' ? 'default' : 'ghost'}
@@ -102,7 +102,7 @@ export default function FileTreeHeader({
title={t('fileTree.simpleView')}
aria-label={t('fileTree.simpleView')}
>
<List className="w-3.5 h-3.5" />
<List className="h-3.5 w-3.5" />
</Button>
<Button
variant={viewMode === 'compact' ? 'default' : 'ghost'}
@@ -112,7 +112,7 @@ export default function FileTreeHeader({
title={t('fileTree.compactView')}
aria-label={t('fileTree.compactView')}
>
<Eye className="w-3.5 h-3.5" />
<Eye className="h-3.5 w-3.5" />
</Button>
<Button
variant={viewMode === 'detailed' ? 'default' : 'ghost'}
@@ -122,31 +122,31 @@ export default function FileTreeHeader({
title={t('fileTree.detailedView')}
aria-label={t('fileTree.detailedView')}
>
<TableProperties className="w-3.5 h-3.5" />
<TableProperties className="h-3.5 w-3.5" />
</Button>
</div>
</div>
{/* Search Bar */}
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
<Input
type="text"
placeholder={t('fileTree.searchPlaceholder')}
value={searchQuery}
onChange={(event) => onSearchQueryChange(event.target.value)}
className="pl-8 pr-8 h-8 text-sm"
className="h-8 pl-8 pr-8 text-sm"
/>
{searchQuery && (
<Button
variant="ghost"
size="sm"
className="absolute right-0.5 top-1/2 -translate-y-1/2 h-5 w-5 p-0 hover:bg-accent"
className="absolute right-0.5 top-1/2 h-5 w-5 -translate-y-1/2 p-0 hover:bg-accent"
onClick={() => onSearchQueryChange('')}
title={t('fileTree.clearSearch')}
aria-label={t('fileTree.clearSearch')}
>
<X className="w-3 h-3" />
<X className="h-3 w-3" />
</Button>
)}
</div>

View File

@@ -4,8 +4,8 @@ export default function FileTreeLoadingState() {
const { t } = useTranslation();
return (
<div className="h-full flex items-center justify-center">
<div className="text-muted-foreground text-sm">{t('fileTree.loading')}</div>
<div className="flex h-full items-center justify-center">
<div className="text-sm text-muted-foreground">{t('fileTree.loading')}</div>
</div>
);
}

View File

@@ -1,9 +1,9 @@
import type { ReactNode, RefObject } from 'react';
import { ChevronRight, Folder, FolderOpen } from 'lucide-react';
import { cn } from '../../../lib/utils';
import FileContextMenu from './FileContextMenu';
import type { FileTreeNode as FileTreeNodeType, FileTreeViewMode } from '../types/types';
import { Input } from '../../../shared/view/ui';
import FileContextMenu from './FileContextMenu';
type FileTreeNodeProps = {
item: FileTreeNodeType;
@@ -40,7 +40,7 @@ type TreeItemIconProps = {
function TreeItemIcon({ item, isOpen, renderFileIcon }: TreeItemIconProps) {
if (item.type === 'directory') {
return (
<span className="flex items-center gap-0.5 flex-shrink-0">
<span className="flex flex-shrink-0 items-center gap-0.5">
<ChevronRight
className={cn(
'w-3.5 h-3.5 text-muted-foreground/70 transition-transform duration-150',
@@ -48,15 +48,15 @@ function TreeItemIcon({ item, isOpen, renderFileIcon }: TreeItemIconProps) {
)}
/>
{isOpen ? (
<FolderOpen className="w-4 h-4 text-blue-500 flex-shrink-0" />
<FolderOpen className="h-4 w-4 flex-shrink-0 text-blue-500" />
) : (
<Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
<Folder className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
)}
</span>
);
}
return <span className="flex items-center flex-shrink-0 ml-[18px]">{renderFileIcon(item.name)}</span>;
return <span className="ml-[18px] flex flex-shrink-0 items-center">{renderFileIcon(item.name)}</span>;
}
export default function FileTreeNode({
@@ -128,7 +128,7 @@ export default function FileTreeNode({
handleConfirmRename();
}, 100);
}}
className="h-6 text-sm flex-1"
className="h-6 flex-1 text-sm"
disabled={operationLoading}
/>
</div>
@@ -143,23 +143,23 @@ export default function FileTreeNode({
>
{viewMode === 'detailed' ? (
<>
<div className="col-span-5 flex items-center gap-1.5 min-w-0">
<div className="col-span-5 flex min-w-0 items-center gap-1.5">
<TreeItemIcon item={item} isOpen={isOpen} renderFileIcon={renderFileIcon} />
<span className={nameClassName}>{item.name}</span>
</div>
<div className="col-span-2 text-sm text-muted-foreground tabular-nums">
<div className="col-span-2 text-sm tabular-nums text-muted-foreground">
{item.type === 'file' ? formatFileSize(item.size) : ''}
</div>
<div className="col-span-3 text-sm text-muted-foreground">{formatRelativeTime(item.modified)}</div>
<div className="col-span-2 text-sm text-muted-foreground font-mono">{item.permissionsRwx || ''}</div>
<div className="col-span-2 font-mono text-sm text-muted-foreground">{item.permissionsRwx || ''}</div>
</>
) : viewMode === 'compact' ? (
<>
<div className="flex items-center gap-1.5 min-w-0">
<div className="flex min-w-0 items-center gap-1.5">
<TreeItemIcon item={item} isOpen={isOpen} renderFileIcon={renderFileIcon} />
<span className={nameClassName}>{item.name}</span>
</div>
<div className="flex items-center gap-3 text-sm text-muted-foreground flex-shrink-0 ml-2">
<div className="ml-2 flex flex-shrink-0 items-center gap-3 text-sm text-muted-foreground">
{item.type === 'file' && (
<>
<span className="tabular-nums">{formatFileSize(item.size)}</span>
@@ -202,7 +202,7 @@ export default function FileTreeNode({
{isDirectory && isOpen && hasChildren && (
<div className="relative">
<span
className="absolute top-0 bottom-0 border-l border-border/40"
className="absolute bottom-0 top-0 border-l border-border/40"
style={{ left: `${level * 16 + 14}px` }}
aria-hidden="true"
/>

View File

@@ -58,16 +58,16 @@ export default function ImageViewer({ file, onClose }: ImageViewerProps) {
}, [imagePath]);
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl max-h-[90vh] w-full mx-4 overflow-hidden">
<div className="flex items-center justify-between p-4 border-b">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="mx-4 max-h-[90vh] w-full max-w-4xl overflow-hidden rounded-lg bg-white shadow-xl dark:bg-gray-800">
<div className="flex items-center justify-between border-b p-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{file.name}</h3>
<Button variant="ghost" size="sm" onClick={onClose} className="h-8 w-8 p-0">
<X className="h-4 w-4" />
</Button>
</div>
<div className="p-4 flex justify-center items-center bg-gray-50 dark:bg-gray-900 min-h-[400px]">
<div className="flex min-h-[400px] items-center justify-center bg-gray-50 p-4 dark:bg-gray-900">
{loading && (
<div className="text-center text-gray-500 dark:text-gray-400">
<p>Loading image...</p>
@@ -77,18 +77,18 @@ export default function ImageViewer({ file, onClose }: ImageViewerProps) {
<img
src={imageUrl}
alt={file.name}
className="max-w-full max-h-[70vh] object-contain rounded-lg shadow-md"
className="max-h-[70vh] max-w-full rounded-lg object-contain shadow-md"
/>
)}
{!loading && !imageUrl && (
<div className="text-center text-gray-500 dark:text-gray-400">
<p>{error || 'Unable to load image'}</p>
<p className="text-sm mt-2 break-all">{file.path}</p>
<p className="mt-2 break-all text-sm">{file.path}</p>
</div>
)}
</div>
<div className="p-4 border-t bg-gray-50 dark:bg-gray-800">
<div className="border-t bg-gray-50 p-4 dark:bg-gray-800">
<p className="text-sm text-gray-600 dark:text-gray-400">{file.path}</p>
</div>
</div>

View File

@@ -66,14 +66,14 @@ export default function GitPanel({ selectedProject, isMobile = false, onFileOpen
if (!selectedProject) {
return (
<div className="h-full flex items-center justify-center text-muted-foreground">
<div className="flex h-full items-center justify-center text-muted-foreground">
<p>Select a project to view source control</p>
</div>
);
}
return (
<div className="h-full flex flex-col bg-background">
<div className="flex h-full flex-col bg-background">
<GitPanelHeader
isMobile={isMobile}
currentBranch={currentBranch}

View File

@@ -113,9 +113,9 @@ export default function GitPanelHeader({
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowBranchDropdown((previous) => !previous)}
className={`flex items-center hover:bg-accent rounded-lg transition-colors ${isMobile ? 'space-x-1 px-2 py-1' : 'space-x-2 px-3 py-1.5'}`}
className={`flex items-center rounded-lg transition-colors hover:bg-accent ${isMobile ? 'space-x-1 px-2 py-1' : 'space-x-2 px-3 py-1.5'}`}
>
<GitBranch className={`text-muted-foreground ${isMobile ? 'w-3 h-3' : 'w-4 h-4'}`} />
<GitBranch className={`text-muted-foreground ${isMobile ? 'h-3 w-3' : 'h-4 w-4'}`} />
<span className="flex items-center gap-1">
<span className={`font-medium ${isMobile ? 'text-xs' : 'text-sm'}`}>{currentBranch}</span>
{remoteStatus?.hasRemote && (
@@ -146,22 +146,22 @@ export default function GitPanelHeader({
</span>
)}
</span>
<ChevronDown className={`w-3 h-3 text-muted-foreground transition-transform ${showBranchDropdown ? 'rotate-180' : ''}`} />
<ChevronDown className={`h-3 w-3 text-muted-foreground transition-transform ${showBranchDropdown ? 'rotate-180' : ''}`} />
</button>
{showBranchDropdown && (
<div className="absolute top-full left-0 mt-1 w-64 bg-card rounded-xl shadow-lg border border-border z-50 overflow-hidden">
<div className="py-1 max-h-64 overflow-y-auto">
<div className="absolute left-0 top-full z-50 mt-1 w-64 overflow-hidden rounded-xl border border-border bg-card shadow-lg">
<div className="max-h-64 overflow-y-auto py-1">
{branches.map((branch) => (
<button
key={branch}
onClick={() => void handleSwitchBranch(branch)}
className={`w-full text-left px-4 py-2 text-sm hover:bg-accent transition-colors ${
className={`w-full px-4 py-2 text-left text-sm transition-colors hover:bg-accent ${
branch === currentBranch ? 'bg-accent/50 text-foreground' : 'text-muted-foreground'
}`}
>
<span className="flex items-center space-x-2">
{branch === currentBranch && <Check className="w-3 h-3 text-primary" />}
{branch === currentBranch && <Check className="h-3 w-3 text-primary" />}
<span className={branch === currentBranch ? 'font-medium' : ''}>{branch}</span>
</span>
</button>
@@ -173,9 +173,9 @@ export default function GitPanelHeader({
setShowNewBranchModal(true);
setShowBranchDropdown(false);
}}
className="w-full text-left px-4 py-2 text-sm hover:bg-accent transition-colors flex items-center space-x-2"
className="flex w-full items-center space-x-2 px-4 py-2 text-left text-sm transition-colors hover:bg-accent"
>
<Plus className="w-3 h-3" />
<Plus className="h-3 w-3" />
<span>Create new branch</span>
</button>
</div>
@@ -190,10 +190,10 @@ export default function GitPanelHeader({
<button
onClick={requestPublishConfirmation}
disabled={isPublishing}
className="px-2.5 py-1 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 flex items-center gap-1 transition-colors"
className="flex items-center gap-1 rounded-lg bg-purple-600 px-2.5 py-1 text-sm text-white transition-colors hover:bg-purple-700 disabled:opacity-50"
title={`Publish branch "${currentBranch}" to ${remoteName}`}
>
<Upload className={`w-3 h-3 ${isPublishing ? 'animate-pulse' : ''}`} />
<Upload className={`h-3 w-3 ${isPublishing ? 'animate-pulse' : ''}`} />
<span>{isPublishing ? 'Publishing...' : 'Publish'}</span>
</button>
)}
@@ -204,10 +204,10 @@ export default function GitPanelHeader({
<button
onClick={requestPullConfirmation}
disabled={isPulling}
className="px-2.5 py-1 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 flex items-center gap-1 transition-colors"
className="flex items-center gap-1 rounded-lg bg-green-600 px-2.5 py-1 text-sm text-white transition-colors hover:bg-green-700 disabled:opacity-50"
title={`Pull ${behindCount} commit${behindCount !== 1 ? 's' : ''} from ${remoteName}`}
>
<Download className={`w-3 h-3 ${isPulling ? 'animate-pulse' : ''}`} />
<Download className={`h-3 w-3 ${isPulling ? 'animate-pulse' : ''}`} />
<span>{isPulling ? 'Pulling...' : `Pull ${behindCount}`}</span>
</button>
)}
@@ -216,10 +216,10 @@ export default function GitPanelHeader({
<button
onClick={requestPushConfirmation}
disabled={isPushing}
className="px-2.5 py-1 text-sm bg-orange-600 text-white rounded-lg hover:bg-orange-700 disabled:opacity-50 flex items-center gap-1 transition-colors"
className="flex items-center gap-1 rounded-lg bg-orange-600 px-2.5 py-1 text-sm text-white transition-colors hover:bg-orange-700 disabled:opacity-50"
title={`Push ${aheadCount} commit${aheadCount !== 1 ? 's' : ''} to ${remoteName}`}
>
<Upload className={`w-3 h-3 ${isPushing ? 'animate-pulse' : ''}`} />
<Upload className={`h-3 w-3 ${isPushing ? 'animate-pulse' : ''}`} />
<span>{isPushing ? 'Pushing...' : `Push ${aheadCount}`}</span>
</button>
)}
@@ -228,10 +228,10 @@ export default function GitPanelHeader({
<button
onClick={() => void handleFetch()}
disabled={isFetching}
className="px-2.5 py-1 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 flex items-center gap-1 transition-colors"
className="flex items-center gap-1 rounded-lg bg-primary px-2.5 py-1 text-sm text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
title={`Fetch from ${remoteName}`}
>
<RefreshCw className={`w-3 h-3 ${isFetching ? 'animate-spin' : ''}`} />
<RefreshCw className={`h-3 w-3 ${isFetching ? 'animate-spin' : ''}`} />
<span>{isFetching ? 'Fetching...' : 'Fetch'}</span>
</button>
)}
@@ -243,10 +243,10 @@ export default function GitPanelHeader({
<button
onClick={onRefresh}
disabled={isLoading}
className={`hover:bg-accent rounded-lg transition-colors ${isMobile ? 'p-1' : 'p-1.5'}`}
className={`rounded-lg transition-colors hover:bg-accent ${isMobile ? 'p-1' : 'p-1.5'}`}
title="Refresh git status"
>
<RefreshCw className={`text-muted-foreground ${isLoading ? 'animate-spin' : ''} ${isMobile ? 'w-3 h-3' : 'w-4 h-4'}`} />
<RefreshCw className={`text-muted-foreground ${isLoading ? 'animate-spin' : ''} ${isMobile ? 'h-3 w-3' : 'h-4 w-4'}`} />
</button>
</div>
</div>

View File

@@ -7,18 +7,18 @@ type GitRepositoryErrorStateProps = {
export default function GitRepositoryErrorState({ error, details }: GitRepositoryErrorStateProps) {
return (
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground px-6 py-12">
<div className="w-16 h-16 rounded-2xl bg-muted/50 flex items-center justify-center mb-6">
<GitBranch className="w-8 h-8 opacity-40" />
<div className="flex flex-1 flex-col items-center justify-center px-6 py-12 text-muted-foreground">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted/50">
<GitBranch className="h-8 w-8 opacity-40" />
</div>
<h3 className="text-lg font-medium mb-3 text-center text-foreground">{error}</h3>
<h3 className="mb-3 text-center text-lg font-medium text-foreground">{error}</h3>
{details && (
<p className="text-sm text-center leading-relaxed mb-6 max-w-md">{details}</p>
<p className="mb-6 max-w-md text-center text-sm leading-relaxed">{details}</p>
)}
<div className="p-4 bg-primary/5 rounded-xl border border-primary/10 max-w-md">
<p className="text-sm text-primary text-center">
<div className="max-w-md rounded-xl border border-primary/10 bg-primary/5 p-4">
<p className="text-center text-sm text-primary">
<strong>Tip:</strong> Run{' '}
<code className="bg-primary/10 px-2 py-1 rounded-md font-mono text-xs">git init</code>{' '}
<code className="rounded-md bg-primary/10 px-2 py-1 font-mono text-xs">git init</code>{' '}
in your project directory to initialize git source control.
</p>
</div>

View File

@@ -11,19 +11,19 @@ export default function GitViewTabs({ activeView, isHidden, onChange }: GitViewT
return (
<div
className={`flex border-b border-border/60 transition-all duration-300 ease-in-out ${
isHidden ? 'max-h-0 opacity-0 -translate-y-2 overflow-hidden' : 'max-h-16 opacity-100 translate-y-0'
isHidden ? 'max-h-0 -translate-y-2 overflow-hidden opacity-0' : 'max-h-16 translate-y-0 opacity-100'
}`}
>
<button
onClick={() => onChange('changes')}
className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
activeView === 'changes'
? 'text-primary border-b-2 border-primary'
? 'border-b-2 border-primary text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<span className="flex items-center justify-center gap-2">
<FileText className="w-4 h-4" />
<FileText className="h-4 w-4" />
<span>Changes</span>
</span>
</button>
@@ -31,12 +31,12 @@ export default function GitViewTabs({ activeView, isHidden, onChange }: GitViewT
onClick={() => onChange('history')}
className={`flex-1 px-4 py-2 text-sm font-medium transition-colors ${
activeView === 'history'
? 'text-primary border-b-2 border-primary'
? 'border-b-2 border-primary text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<span className="flex items-center justify-center gap-2">
<History className="w-4 h-4" />
<History className="h-4 w-4" />
<span>History</span>
</span>
</button>

View File

@@ -153,39 +153,39 @@ export default function ChangesView({
<div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-mobile-nav' : ''}`}>
{isLoading ? (
<div className="flex items-center justify-center h-32">
<RefreshCw className="w-5 h-5 animate-spin text-muted-foreground" />
<div className="flex h-32 items-center justify-center">
<RefreshCw className="h-5 w-5 animate-spin text-muted-foreground" />
</div>
) : gitStatus?.hasCommits === false ? (
<div className="flex flex-col items-center justify-center p-8 text-center">
<div className="w-14 h-14 rounded-2xl bg-muted/50 flex items-center justify-center mb-4">
<GitBranch className="w-7 h-7 text-muted-foreground/50" />
<div className="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-muted/50">
<GitBranch className="h-7 w-7 text-muted-foreground/50" />
</div>
<h3 className="text-lg font-medium mb-2 text-foreground">No commits yet</h3>
<p className="text-sm text-muted-foreground mb-6 max-w-md">
<h3 className="mb-2 text-lg font-medium text-foreground">No commits yet</h3>
<p className="mb-6 max-w-md text-sm text-muted-foreground">
This repository doesn&apos;t have any commits yet. Create your first commit to start tracking changes.
</p>
<button
onClick={() => void onCreateInitialCommit()}
disabled={isCreatingInitialCommit}
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 transition-colors"
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
{isCreatingInitialCommit ? (
<>
<RefreshCw className="w-4 h-4 animate-spin" />
<RefreshCw className="h-4 w-4 animate-spin" />
<span>Creating Initial Commit...</span>
</>
) : (
<>
<GitCommit className="w-4 h-4" />
<GitCommit className="h-4 w-4" />
<span>Create Initial Commit</span>
</>
)}
</button>
</div>
) : !gitStatus || !hasChangedFiles(gitStatus) ? (
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
<GitCommit className="w-10 h-10 mb-2 opacity-40" />
<div className="flex h-32 flex-col items-center justify-center text-muted-foreground">
<GitCommit className="mb-2 h-10 w-10 opacity-40" />
<p className="text-sm">No changes detected</p>
</div>
) : (

View File

@@ -77,30 +77,30 @@ export default function CommitComposer({
return (
<div
className={`transition-all duration-300 ease-in-out ${
isHidden ? 'max-h-0 opacity-0 -translate-y-2 overflow-hidden' : 'max-h-96 opacity-100 translate-y-0'
isHidden ? 'max-h-0 -translate-y-2 overflow-hidden opacity-0' : 'max-h-96 translate-y-0 opacity-100'
}`}
>
{isMobile && isCollapsed ? (
<div className="px-4 py-2 border-b border-border/60">
<div className="border-b border-border/60 px-4 py-2">
<button
onClick={() => setIsCollapsed(false)}
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
className="flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-3 py-2 text-sm text-primary-foreground transition-colors hover:bg-primary/90"
>
<GitCommit className="w-4 h-4" />
<GitCommit className="h-4 w-4" />
<span>Commit {selectedFileCount} file{selectedFileCount !== 1 ? 's' : ''}</span>
<ChevronDown className="w-3 h-3" />
<ChevronDown className="h-3 w-3" />
</button>
</div>
) : (
<div className="px-4 py-3 border-b border-border/60">
<div className="border-b border-border/60 px-4 py-3">
{isMobile && (
<div className="flex items-center justify-between mb-2">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-medium text-foreground">Commit Changes</span>
<button
onClick={() => setIsCollapsed(true)}
className="p-1 hover:bg-accent rounded-lg transition-colors"
className="rounded-lg p-1 transition-colors hover:bg-accent"
>
<ChevronDown className="w-4 h-4 rotate-180" />
<ChevronDown className="h-4 w-4 rotate-180" />
</button>
</div>
)}
@@ -110,7 +110,7 @@ export default function CommitComposer({
value={commitMessage}
onChange={(event) => setCommitMessage(event.target.value)}
placeholder="Message (Ctrl+Enter to commit)"
className="w-full px-3 py-2 text-sm border border-border rounded-xl bg-background text-foreground placeholder:text-muted-foreground resize-none pr-20 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30"
className="w-full resize-none rounded-xl border border-border bg-background px-3 py-2 pr-20 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary/30 focus:outline-none focus:ring-2 focus:ring-primary/20"
rows={3}
onKeyDown={(event) => {
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
@@ -123,13 +123,13 @@ export default function CommitComposer({
<button
onClick={() => void handleGenerateMessage()}
disabled={selectedFileCount === 0 || isGeneratingMessage}
className="p-1.5 text-muted-foreground hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
className="p-1.5 text-muted-foreground transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
title="Generate commit message"
>
{isGeneratingMessage ? (
<RefreshCw className="w-4 h-4 animate-spin" />
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
<Sparkles className="w-4 h-4" />
<Sparkles className="h-4 w-4" />
)}
</button>
<div style={{ display: 'none' }}>
@@ -142,16 +142,16 @@ export default function CommitComposer({
</div>
</div>
<div className="flex items-center justify-between mt-2">
<div className="mt-2 flex items-center justify-between">
<span className="text-sm text-muted-foreground">
{selectedFileCount} file{selectedFileCount !== 1 ? 's' : ''} selected
</span>
<button
onClick={requestCommitConfirmation}
disabled={!commitMessage.trim() || selectedFileCount === 0 || isCommitting}
className="px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-1 transition-colors"
className="flex items-center space-x-1 rounded-lg bg-primary px-3 py-1.5 text-sm text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
<Check className="w-3 h-3" />
<Check className="h-3 w-3" />
<span>{isCommitting ? 'Committing...' : 'Commit'}</span>
</button>
</div>

View File

@@ -37,25 +37,25 @@ export default function FileChangeItem({
return (
<div className="border-b border-border last:border-0">
<div className={`flex items-center hover:bg-accent/50 transition-colors ${isMobile ? 'px-2 py-1.5' : 'px-3 py-2'}`}>
<div className={`flex items-center transition-colors hover:bg-accent/50 ${isMobile ? 'px-2 py-1.5' : 'px-3 py-2'}`}>
<input
type="checkbox"
checked={isSelected}
onChange={() => onToggleSelected(filePath)}
onClick={(event) => event.stopPropagation()}
className={`rounded border-border text-primary focus:ring-primary/40 bg-background checked:bg-primary ${isMobile ? 'mr-1.5' : 'mr-2'}`}
className={`rounded border-border bg-background text-primary checked:bg-primary focus:ring-primary/40 ${isMobile ? 'mr-1.5' : 'mr-2'}`}
/>
<div className="flex items-center flex-1 min-w-0">
<div className="flex min-w-0 flex-1 items-center">
<button
onClick={(event) => {
event.stopPropagation();
onToggleExpanded(filePath);
}}
className={`p-0.5 hover:bg-accent rounded cursor-pointer ${isMobile ? 'mr-1' : 'mr-2'}`}
className={`cursor-pointer rounded p-0.5 hover:bg-accent ${isMobile ? 'mr-1' : 'mr-2'}`}
title={isExpanded ? 'Collapse diff' : 'Expand diff'}
>
<ChevronRight className={`w-3 h-3 transition-transform duration-200 ease-in-out ${isExpanded ? 'rotate-90' : 'rotate-0'}`} />
<ChevronRight className={`h-3 w-3 transition-transform duration-200 ease-in-out ${isExpanded ? 'rotate-90' : 'rotate-0'}`} />
</button>
<span
@@ -76,16 +76,16 @@ export default function FileChangeItem({
event.stopPropagation();
onRequestFileAction(filePath, status);
}}
className={`${isMobile ? 'px-2 py-1 text-xs' : 'p-1'} hover:bg-destructive/10 rounded text-destructive font-medium flex items-center gap-1`}
className={`${isMobile ? 'px-2 py-1 text-xs' : 'p-1'} flex items-center gap-1 rounded font-medium text-destructive hover:bg-destructive/10`}
title={status === 'U' ? 'Delete untracked file' : 'Discard changes'}
>
<Trash2 className="w-3 h-3" />
<Trash2 className="h-3 w-3" />
{isMobile && <span>{status === 'U' ? 'Delete' : 'Discard'}</span>}
</button>
)}
<span
className={`inline-flex items-center justify-center w-5 h-5 rounded text-[10px] font-bold border ${badgeClass}`}
className={`inline-flex h-5 w-5 items-center justify-center rounded border text-[10px] font-bold ${badgeClass}`}
title={statusLabel}
>
{status}
@@ -95,12 +95,12 @@ export default function FileChangeItem({
</div>
<div
className={`bg-muted/50 transition-all duration-400 ease-in-out overflow-hidden ${isExpanded && diff ? 'max-h-[600px] opacity-100 translate-y-0' : 'max-h-0 opacity-0 -translate-y-1'
className={`duration-400 overflow-hidden bg-muted/50 transition-all ease-in-out ${isExpanded && diff ? 'max-h-[600px] translate-y-0 opacity-100' : 'max-h-0 -translate-y-1 opacity-0'
}`}
>
<div className="flex items-center justify-between p-2 border-b border-border">
<div className="flex items-center justify-between border-b border-border p-2">
<span className="flex items-center gap-2">
<span className={`inline-flex items-center justify-center w-5 h-5 rounded text-[10px] font-bold border ${badgeClass}`}>
<span className={`inline-flex h-5 w-5 items-center justify-center rounded border text-[10px] font-bold ${badgeClass}`}>
{status}
</span>
<span className="text-sm font-medium text-foreground">{statusLabel}</span>
@@ -111,7 +111,7 @@ export default function FileChangeItem({
event.stopPropagation();
onToggleWrapText();
}}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
title={wrapText ? 'Switch to horizontal scroll' : 'Switch to text wrap'}
>
{wrapText ? 'Scroll' : 'Wrap'}

View File

@@ -17,9 +17,9 @@ export default function FileSelectionControls({
}: FileSelectionControlsProps) {
return (
<div
className={`border-b border-border/60 flex items-center justify-between transition-all duration-300 ease-in-out ${
className={`flex items-center justify-between border-b border-border/60 transition-all duration-300 ease-in-out ${
isMobile ? 'px-3 py-1.5' : 'px-4 py-2'
} ${isHidden ? 'max-h-0 opacity-0 -translate-y-2 overflow-hidden' : 'max-h-16 opacity-100 translate-y-0'}`}
} ${isHidden ? 'max-h-0 -translate-y-2 overflow-hidden opacity-0' : 'max-h-16 translate-y-0 opacity-100'}`}
>
<span className="text-sm text-muted-foreground">
{selectedCount} of {totalCount} {isMobile ? '' : 'files'} selected
@@ -27,14 +27,14 @@ export default function FileSelectionControls({
<span className={`flex ${isMobile ? 'gap-1' : 'gap-2'}`}>
<button
onClick={onSelectAll}
className="text-sm text-primary hover:text-primary/80 transition-colors"
className="text-sm text-primary transition-colors hover:text-primary/80"
>
{isMobile ? 'All' : 'Select All'}
</button>
<span className="text-border">|</span>
<button
onClick={onDeselectAll}
className="text-sm text-primary hover:text-primary/80 transition-colors"
className="text-sm text-primary transition-colors hover:text-primary/80"
>
{isMobile ? 'None' : 'Deselect All'}
</button>

View File

@@ -24,24 +24,24 @@ export default function FileStatusLegend({ isMobile }: FileStatusLegendProps) {
<div className="border-b border-border/60">
<button
onClick={() => setIsOpen((previous) => !previous)}
className="w-full px-4 py-2 bg-muted/30 hover:bg-muted/50 text-sm text-muted-foreground flex items-center justify-center gap-1 transition-colors"
className="flex w-full items-center justify-center gap-1 bg-muted/30 px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted/50"
>
<Info className="w-3 h-3" />
<Info className="h-3 w-3" />
<span>File Status Guide</span>
{isOpen ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
{isOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
</button>
{isOpen && (
<div className="px-4 py-3 bg-muted/30 text-sm">
<div className="bg-muted/30 px-4 py-3 text-sm">
<div className="flex justify-center gap-6">
{LEGEND_ITEMS.map((item) => (
<span key={item.status} className="flex items-center gap-2">
<span
className={`inline-flex items-center justify-center w-5 h-5 rounded border font-bold text-[10px] ${getStatusBadgeClass(item.status)}`}
className={`inline-flex h-5 w-5 items-center justify-center rounded border text-[10px] font-bold ${getStatusBadgeClass(item.status)}`}
>
{item.status}
</span>
<span className="text-muted-foreground italic">{item.label}</span>
<span className="italic text-muted-foreground">{item.label}</span>
</span>
))}
</div>

View File

@@ -1,5 +1,4 @@
import { ChevronDown, ChevronRight } from 'lucide-react';
import type { GitCommitSummary } from '../../types/types';
import GitDiffViewer from '../shared/GitDiffViewer';
@@ -26,23 +25,23 @@ export default function CommitHistoryItem({
<button
type="button"
aria-expanded={isExpanded}
className="w-full flex items-start p-3 hover:bg-accent/50 cursor-pointer transition-colors text-left bg-transparent border-0"
className="flex w-full cursor-pointer items-start border-0 bg-transparent p-3 text-left transition-colors hover:bg-accent/50"
onClick={onToggle}
>
<span className="mr-2 mt-1 p-0.5 hover:bg-accent rounded">
{isExpanded ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
<span className="mr-2 mt-1 rounded p-0.5 hover:bg-accent">
{isExpanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
</span>
<div className="flex-1 min-w-0">
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">{commit.message}</p>
<p className="text-sm text-muted-foreground mt-1">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-foreground">{commit.message}</p>
<p className="mt-1 text-sm text-muted-foreground">
{commit.author}
{' \u2022 '}
{commit.date}
</p>
</div>
<span className="text-sm font-mono text-muted-foreground/60 flex-shrink-0">
<span className="flex-shrink-0 font-mono text-sm text-muted-foreground/60">
{commit.hash.substring(0, 7)}
</span>
</div>
@@ -52,7 +51,7 @@ export default function CommitHistoryItem({
{isExpanded && diff && (
<div className="bg-muted/50">
<div className="max-h-96 overflow-y-auto p-2">
<div className="text-sm font-mono text-muted-foreground mb-2">
<div className="mb-2 font-mono text-sm text-muted-foreground">
{commit.stats}
</div>
<GitDiffViewer diff={diff} isMobile={isMobile} wrapText={wrapText} />

View File

@@ -49,12 +49,12 @@ export default function HistoryView({
return (
<div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-mobile-nav' : ''}`}>
{isLoading ? (
<div className="flex items-center justify-center h-32">
<RefreshCw className="w-5 h-5 animate-spin text-muted-foreground" />
<div className="flex h-32 items-center justify-center">
<RefreshCw className="h-5 w-5 animate-spin text-muted-foreground" />
</div>
) : recentCommits.length === 0 ? (
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
<History className="w-10 h-10 mb-2 opacity-40" />
<div className="flex h-32 flex-col items-center justify-center text-muted-foreground">
<History className="mb-2 h-10 w-10 opacity-40" />
<p className="text-sm">No commits found</p>
</div>
) : (

View File

@@ -16,18 +16,18 @@ type ConfirmActionModalProps = {
function renderConfirmActionIcon(actionType: ConfirmationRequest['type']) {
if (actionType === 'discard' || actionType === 'delete') {
return <Trash2 className="w-4 h-4" />;
return <Trash2 className="h-4 w-4" />;
}
if (actionType === 'commit') {
return <Check className="w-4 h-4" />;
return <Check className="h-4 w-4" />;
}
if (actionType === 'pull') {
return <Download className="w-4 h-4" />;
return <Download className="h-4 w-4" />;
}
return <Upload className="w-4 h-4" />;
return <Upload className="h-4 w-4" />;
}
export default function ConfirmActionModal({ action, onCancel, onConfirm }: ConfirmActionModalProps) {
@@ -58,14 +58,14 @@ export default function ConfirmActionModal({ action, onCancel, onConfirm }: Conf
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm" onClick={onCancel} />
<div
className="relative bg-card border border-border rounded-xl shadow-2xl max-w-md w-full overflow-hidden"
className="relative w-full max-w-md overflow-hidden rounded-xl border border-border bg-card shadow-2xl"
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
>
<div className="p-6">
<div className="flex items-center mb-4">
<div className={`p-2 rounded-full mr-3 ${CONFIRMATION_ICON_CONTAINER_CLASSES[action.type]}`}>
<div className="mb-4 flex items-center">
<div className={`mr-3 rounded-full p-2 ${CONFIRMATION_ICON_CONTAINER_CLASSES[action.type]}`}>
{renderConfirmActionIcon(action.type)}
</div>
<h3 id={titleId} className="text-lg font-semibold text-foreground">
@@ -73,18 +73,18 @@ export default function ConfirmActionModal({ action, onCancel, onConfirm }: Conf
</h3>
</div>
<p className="text-sm text-muted-foreground mb-6">{action.message}</p>
<p className="mb-6 text-sm text-muted-foreground">{action.message}</p>
<div className="flex justify-end space-x-3">
<button
onClick={onCancel}
className="px-4 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg transition-colors"
className="rounded-lg px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
onClick={onConfirm}
className={`px-4 py-2 text-sm text-white rounded-lg transition-colors flex items-center space-x-2 ${CONFIRMATION_BUTTON_CLASSES[action.type]}`}
className={`flex items-center space-x-2 rounded-lg px-4 py-2 text-sm text-white transition-colors ${CONFIRMATION_BUTTON_CLASSES[action.type]}`}
>
{renderConfirmActionIcon(action.type)}
<span>{CONFIRMATION_ACTION_LABELS[action.type]}</span>

View File

@@ -51,16 +51,16 @@ export default function NewBranchModal({
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
<div
className="relative bg-card border border-border rounded-xl shadow-2xl max-w-md w-full overflow-hidden"
className="relative w-full max-w-md overflow-hidden rounded-xl border border-border bg-card shadow-2xl"
role="dialog"
aria-modal="true"
aria-labelledby="new-branch-title"
>
<div className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-4">Create New Branch</h3>
<h3 className="mb-4 text-lg font-semibold text-foreground">Create New Branch</h3>
<div className="mb-4">
<label htmlFor="git-new-branch-name" className="block text-sm font-medium text-foreground/80 mb-2">
<label htmlFor="git-new-branch-name" className="mb-2 block text-sm font-medium text-foreground/80">
Branch Name
</label>
<input
@@ -83,35 +83,35 @@ export default function NewBranchModal({
}
}}
placeholder="feature/new-feature"
className="w-full px-3 py-2 border border-border rounded-xl bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30"
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary/30 focus:outline-none focus:ring-2 focus:ring-primary/20"
autoFocus
/>
</div>
<p className="text-sm text-muted-foreground mb-4">
<p className="mb-4 text-sm text-muted-foreground">
This will create a new branch from the current branch ({currentBranch})
</p>
<div className="flex justify-end space-x-3">
<button
onClick={onClose}
className="px-4 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg transition-colors"
className="rounded-lg px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
onClick={() => void handleCreateBranch()}
disabled={!newBranchName.trim() || isCreatingBranch}
className="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2 transition-colors"
className="flex items-center space-x-2 rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
{isCreatingBranch ? (
<>
<RefreshCw className="w-3 h-3 animate-spin" />
<RefreshCw className="h-3 w-3 animate-spin" />
<span>Creating...</span>
</>
) : (
<>
<Plus className="w-3 h-3" />
<Plus className="h-3 w-3" />
<span>Create Branch</span>
</>
)}

View File

@@ -7,7 +7,7 @@ type GitDiffViewerProps = {
export default function GitDiffViewer({ diff, isMobile, wrapText }: GitDiffViewerProps) {
if (!diff) {
return (
<div className="p-4 text-center text-muted-foreground text-sm">
<div className="p-4 text-center text-sm text-muted-foreground">
No diff available
</div>
);
@@ -21,9 +21,9 @@ export default function GitDiffViewer({ diff, isMobile, wrapText }: GitDiffViewe
return (
<div
key={index}
className={`font-mono text-xs px-3 py-0.5 ${isMobile && wrapText ? 'whitespace-pre-wrap break-all' : 'whitespace-pre overflow-x-auto'
} ${isAddition ? 'bg-green-50 dark:bg-green-950/50 text-green-700 dark:text-green-300' :
isDeletion ? 'bg-red-50 dark:bg-red-950/50 text-red-700 dark:text-red-300' :
className={`px-3 py-0.5 font-mono text-xs ${isMobile && wrapText ? 'whitespace-pre-wrap break-all' : 'overflow-x-auto whitespace-pre'
} ${isAddition ? 'bg-green-50 text-green-700 dark:bg-green-950/50 dark:text-green-300' :
isDeletion ? 'bg-red-50 text-red-700 dark:bg-red-950/50 dark:text-red-300' :
isHeader ? 'bg-primary/5 text-primary' :
'text-muted-foreground/70'
}`}

View File

@@ -32,8 +32,8 @@ function ErrorFallback({
}: ErrorFallbackProps) {
return (
<div className="flex flex-col items-center justify-center p-8 text-center">
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
<div className="flex items-center mb-4">
<div className="max-w-md rounded-lg border border-red-200 bg-red-50 p-6">
<div className="mb-4 flex items-center">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path
@@ -49,8 +49,8 @@ function ErrorFallback({
<p className="mb-2">An error occurred while loading the chat interface.</p>
{showDetails && (
<details className="mt-4">
<summary className="cursor-pointer text-xs font-mono">Error Details</summary>
<pre className="mt-2 text-xs bg-red-100 p-2 rounded overflow-auto max-h-40">
<summary className="cursor-pointer font-mono text-xs">Error Details</summary>
<pre className="mt-2 max-h-40 overflow-auto rounded bg-red-100 p-2 text-xs">
{formatError(error)}
{componentStack}
</pre>
@@ -60,7 +60,7 @@ function ErrorFallback({
<div className="mt-4">
<button
onClick={resetErrorBoundary}
className="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
className="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
>
Try Again
</button>

View File

@@ -1,15 +1,9 @@
import React, { useEffect } from 'react';
import ChatInterface from '../../chat/view/ChatInterface';
import FileTree from '../../file-tree/view/FileTree';
import StandaloneShell from '../../standalone-shell/view/StandaloneShell';
import GitPanel from '../../git-panel/view/GitPanel';
import ErrorBoundary from './ErrorBoundary';
import MainContentHeader from './subcomponents/MainContentHeader';
import MainContentStateView from './subcomponents/MainContentStateView';
import type { MainContentProps } from '../types/types';
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
import { useUiPreferences } from '../../../hooks/useUiPreferences';
@@ -17,6 +11,9 @@ import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
import EditorSidebar from '../../code-editor/view/EditorSidebar';
import type { Project } from '../../../types/app';
import { TaskMasterPanel } from '../../task-master';
import MainContentHeader from './subcomponents/MainContentHeader';
import MainContentStateView from './subcomponents/MainContentStateView';
import ErrorBoundary from './ErrorBoundary';
type TaskMasterContextValue = {
currentProject?: Project | null;
@@ -98,7 +95,7 @@ function MainContent({
}
return (
<div className="h-full flex flex-col">
<div className="flex h-full flex-col">
<MainContentHeader
activeTab={activeTab}
setActiveTab={setActiveTab}
@@ -109,8 +106,8 @@ function MainContent({
onMenuClick={onMenuClick}
/>
<div className="flex-1 flex min-h-0 overflow-hidden">
<div className={`flex flex-col min-h-0 min-w-[200px] overflow-hidden ${editorExpanded ? 'hidden' : ''} flex-1`}>
<div className="flex min-h-0 flex-1 overflow-hidden">
<div className={`flex min-h-0 min-w-[200px] flex-col overflow-hidden ${editorExpanded ? 'hidden' : ''} flex-1`}>
<div className={`h-full ${activeTab === 'chat' ? 'block' : 'hidden'}`}>
<ErrorBoundary showDetails>
<ChatInterface

View File

@@ -1,7 +1,7 @@
import type { MainContentHeaderProps } from '../../types/types';
import MobileMenuButton from './MobileMenuButton';
import MainContentTabSwitcher from './MainContentTabSwitcher';
import MainContentTitle from './MainContentTitle';
import type { MainContentHeaderProps } from '../../types/types';
export default function MainContentHeader({
activeTab,
@@ -13,9 +13,9 @@ export default function MainContentHeader({
onMenuClick,
}: MainContentHeaderProps) {
return (
<div className="bg-background border-b border-border/60 px-3 py-1.5 sm:px-4 sm:py-2 pwa-header-safe flex-shrink-0">
<div className="pwa-header-safe flex-shrink-0 border-b border-border/60 bg-background px-3 py-1.5 sm:px-4 sm:py-2">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2 min-w-0 flex-1">
<div className="flex min-w-0 flex-1 items-center gap-2">
{isMobile && <MobileMenuButton onMenuClick={onMenuClick} />}
<MainContentTitle
activeTab={activeTab}
@@ -25,7 +25,7 @@ export default function MainContentHeader({
/>
</div>
<div className="flex-shrink-0 hidden sm:block">
<div className="hidden flex-shrink-0 sm:block">
<MainContentTabSwitcher
activeTab={activeTab}
setActiveTab={setActiveTab}

View File

@@ -1,7 +1,7 @@
import { Folder } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import MobileMenuButton from './MobileMenuButton';
import type { MainContentStateViewProps } from '../../types/types';
import MobileMenuButton from './MobileMenuButton';
export default function MainContentStateView({ mode, isMobile, onMenuClick }: MainContentStateViewProps) {
const { t } = useTranslation();
@@ -9,19 +9,19 @@ export default function MainContentStateView({ mode, isMobile, onMenuClick }: Ma
const isLoading = mode === 'loading';
return (
<div className="h-full flex flex-col">
<div className="flex h-full flex-col">
{isMobile && (
<div className="bg-background/80 backdrop-blur-sm border-b border-border/50 p-2 sm:p-3 pwa-header-safe flex-shrink-0">
<div className="pwa-header-safe flex-shrink-0 border-b border-border/50 bg-background/80 p-2 backdrop-blur-sm sm:p-3">
<MobileMenuButton onMenuClick={onMenuClick} compact />
</div>
)}
{isLoading ? (
<div className="flex-1 flex items-center justify-center">
<div className="flex flex-1 items-center justify-center">
<div className="text-center text-muted-foreground">
<div className="w-10 h-10 mx-auto mb-4">
<div className="mx-auto mb-4 h-10 w-10">
<div
className="w-full h-full rounded-full border-[3px] border-muted border-t-primary"
className="h-full w-full rounded-full border-[3px] border-muted border-t-primary"
style={{
animation: 'spin 1s linear infinite',
WebkitAnimation: 'spin 1s linear infinite',
@@ -29,19 +29,19 @@ export default function MainContentStateView({ mode, isMobile, onMenuClick }: Ma
}}
/>
</div>
<h2 className="text-lg font-semibold text-foreground mb-1">{t('mainContent.loading')}</h2>
<h2 className="mb-1 text-lg font-semibold text-foreground">{t('mainContent.loading')}</h2>
<p className="text-sm">{t('mainContent.settingUpWorkspace')}</p>
</div>
</div>
) : (
<div className="flex-1 flex items-center justify-center">
<div className="text-center max-w-md mx-auto px-6">
<div className="w-14 h-14 mx-auto mb-5 bg-muted/50 rounded-2xl flex items-center justify-center">
<Folder className="w-7 h-7 text-muted-foreground" />
<div className="flex flex-1 items-center justify-center">
<div className="mx-auto max-w-md px-6 text-center">
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl bg-muted/50">
<Folder className="h-7 w-7 text-muted-foreground" />
</div>
<h2 className="text-xl font-semibold mb-2 text-foreground">{t('mainContent.chooseProject')}</h2>
<p className="text-sm text-muted-foreground mb-5 leading-relaxed">{t('mainContent.selectProjectDescription')}</p>
<div className="bg-primary/5 rounded-xl p-3.5 border border-primary/10">
<h2 className="mb-2 text-xl font-semibold text-foreground">{t('mainContent.chooseProject')}</h2>
<p className="mb-5 text-sm leading-relaxed text-muted-foreground">{t('mainContent.selectProjectDescription')}</p>
<div className="rounded-xl border border-primary/10 bg-primary/5 p-3.5">
<p className="text-sm text-primary">
<strong>{t('mainContent.tip')}:</strong> {isMobile ? t('mainContent.createProjectMobile') : t('mainContent.createProjectDesktop')}
</p>

View File

@@ -1,8 +1,8 @@
import { MessageSquare, Terminal, Folder, GitBranch, ClipboardCheck, type LucideIcon } from 'lucide-react';
import { Tooltip } from '../../../../shared/view/ui';
import type { AppTab } from '../../../../types/app';
import type { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { Tooltip } from '../../../../shared/view/ui';
import type { AppTab } from '../../../../types/app';
type MainContentTabSwitcherProps = {
activeTab: AppTab;
@@ -39,7 +39,7 @@ export default function MainContentTabSwitcher({
const tabs = shouldShowTasksTab ? [...BASE_TABS, TASKS_TAB] : BASE_TABS;
return (
<div className="inline-flex items-center bg-muted/60 rounded-lg p-[3px] gap-[2px]">
<div className="inline-flex items-center gap-[2px] rounded-lg bg-muted/60 p-[3px]">
{tabs.map((tab) => {
const Icon = tab.icon;
const isActive = tab.id === activeTab;
@@ -48,13 +48,13 @@ export default function MainContentTabSwitcher({
<Tooltip key={tab.id} content={t(tab.labelKey)} position="bottom">
<button
onClick={() => setActiveTab(tab.id)}
className={`relative flex items-center gap-1.5 px-2.5 py-[5px] text-sm font-medium rounded-md transition-all duration-150 ${
className={`relative flex items-center gap-1.5 rounded-md px-2.5 py-[5px] text-sm font-medium transition-all duration-150 ${
isActive
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<Icon className="w-3.5 h-3.5" strokeWidth={isActive ? 2.2 : 1.8} />
<Icon className="h-3.5 w-3.5" strokeWidth={isActive ? 2.2 : 1.8} />
<span className="hidden lg:inline">{t(tab.labelKey)}</span>
</button>
</Tooltip>

View File

@@ -45,32 +45,32 @@ export default function MainContentTitle({
const showChatNewSession = activeTab === 'chat' && !selectedSession;
return (
<div className="min-w-0 flex items-center gap-2 flex-1 overflow-x-auto scrollbar-hide">
<div className="scrollbar-hide flex min-w-0 flex-1 items-center gap-2 overflow-x-auto">
{showSessionIcon && (
<div className="w-5 h-5 flex-shrink-0 flex items-center justify-center">
<SessionProviderLogo provider={selectedSession?.__provider} className="w-4 h-4" />
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
<SessionProviderLogo provider={selectedSession?.__provider} className="h-4 w-4" />
</div>
)}
<div className="min-w-0 flex-1">
{activeTab === 'chat' && selectedSession ? (
<div className="min-w-0">
<h2 className="text-sm font-semibold text-foreground whitespace-nowrap overflow-x-auto scrollbar-hide leading-tight">
<h2 className="scrollbar-hide overflow-x-auto whitespace-nowrap text-sm font-semibold leading-tight text-foreground">
{getSessionTitle(selectedSession)}
</h2>
<div className="text-[11px] text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
<div className="truncate text-[11px] leading-tight text-muted-foreground">{selectedProject.displayName}</div>
</div>
) : showChatNewSession ? (
<div className="min-w-0">
<h2 className="text-base font-semibold text-foreground leading-tight">{t('mainContent.newSession')}</h2>
<div className="text-xs text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
<h2 className="text-base font-semibold leading-tight text-foreground">{t('mainContent.newSession')}</h2>
<div className="truncate text-xs leading-tight text-muted-foreground">{selectedProject.displayName}</div>
</div>
) : (
<div className="min-w-0">
<h2 className="text-sm font-semibold text-foreground leading-tight">
<h2 className="text-sm font-semibold leading-tight text-foreground">
{getTabTitle(activeTab, shouldShowTasksTab, t)}
</h2>
<div className="text-[11px] text-muted-foreground truncate leading-tight">{selectedProject.displayName}</div>
<div className="truncate text-[11px] leading-tight text-muted-foreground">{selectedProject.displayName}</div>
</div>
)}
</div>

View File

@@ -15,7 +15,7 @@ export default function MobileMenuButton({ onMenuClick, compact = false }: Mobil
className={buttonClasses}
aria-label="Open menu"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>

View File

@@ -13,22 +13,22 @@ type MicButtonViewProps = {
const getButtonIcon = (state: MicButtonState, isSupported: boolean): ReactElement => {
if (!isSupported) {
return <Mic className="w-5 h-5" />;
return <Mic className="h-5 w-5" />;
}
if (state === MIC_BUTTON_STATES.TRANSCRIBING) {
return <Loader2 className="w-5 h-5 animate-spin" />;
return <Loader2 className="h-5 w-5 animate-spin" />;
}
if (state === MIC_BUTTON_STATES.PROCESSING) {
return <Brain className="w-5 h-5 animate-pulse" />;
return <Brain className="h-5 w-5 animate-pulse" />;
}
if (state === MIC_BUTTON_STATES.RECORDING) {
return <Mic className="w-5 h-5 text-white" />;
return <Mic className="h-5 w-5 text-white" />;
}
return <Mic className="w-5 h-5" />;
return <Mic className="h-5 w-5" />;
};
export default function MicButtonView({
@@ -47,12 +47,12 @@ export default function MicButtonView({
type="button"
style={{ backgroundColor: BUTTON_BACKGROUND_BY_STATE[state] }}
className={`
flex items-center justify-center
w-12 h-12 rounded-full
text-white transition-all duration-200
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
touch-action-manipulation flex h-12
w-12 items-center justify-center
rounded-full text-white transition-all
duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500
focus:ring-offset-2
dark:ring-offset-gray-800
touch-action-manipulation
${isDisabled ? 'cursor-not-allowed opacity-75' : 'cursor-pointer'}
${state === MIC_BUTTON_STATES.RECORDING ? 'animate-pulse' : ''}
hover:opacity-90
@@ -66,20 +66,20 @@ export default function MicButtonView({
{error && (
<div
className="absolute top-full mt-2 left-1/2 transform -translate-x-1/2
bg-red-500 text-white text-xs px-2 py-1 rounded whitespace-nowrap z-10
animate-fade-in"
className="animate-fade-in absolute left-1/2 top-full z-10 mt-2
-translate-x-1/2 transform whitespace-nowrap rounded bg-red-500 px-2 py-1 text-xs
text-white"
>
{error}
</div>
)}
{state === MIC_BUTTON_STATES.RECORDING && (
<div className="absolute -inset-1 rounded-full border-2 border-red-500 animate-ping pointer-events-none" />
<div className="pointer-events-none absolute -inset-1 animate-ping rounded-full border-2 border-red-500" />
)}
{state === MIC_BUTTON_STATES.PROCESSING && (
<div className="absolute -inset-1 rounded-full border-2 border-purple-500 animate-ping pointer-events-none" />
<div className="pointer-events-none absolute -inset-1 animate-ping rounded-full border-2 border-purple-500" />
)}
</div>
);

View File

@@ -194,11 +194,11 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
return (
<>
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="w-full max-w-2xl">
<OnboardingStepProgress currentStep={currentStep} />
<div className="bg-card rounded-lg shadow-lg border border-border p-8">
<div className="rounded-lg border border-border bg-card p-8 shadow-lg">
{currentStep === 0 ? (
<GitConfigurationStep
gitName={gitName}
@@ -215,18 +215,18 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
)}
{errorMessage && (
<div className="mt-6 p-4 bg-red-100 dark:bg-red-900/20 border border-red-300 dark:border-red-800 rounded-lg">
<div className="mt-6 rounded-lg border border-red-300 bg-red-100 p-4 dark:border-red-800 dark:bg-red-900/20">
<p className="text-sm text-red-700 dark:text-red-400">{errorMessage}</p>
</div>
)}
<div className="flex items-center justify-between mt-8 pt-6 border-t border-border">
<div className="mt-8 flex items-center justify-between border-t border-border pt-6">
<button
onClick={handlePreviousStep}
disabled={currentStep === 0 || isSubmitting}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground transition-colors duration-200 hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
>
<ChevronLeft className="w-4 h-4" />
<ChevronLeft className="h-4 w-4" />
Previous
</button>
@@ -235,17 +235,17 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
<button
onClick={handleNextStep}
disabled={!isCurrentStepValid || isSubmitting}
className="flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors duration-200"
className="flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-400"
>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin" />
Saving...
</>
) : (
<>
Next
<ChevronRight className="w-4 h-4" />
<ChevronRight className="h-4 w-4" />
</>
)}
</button>
@@ -253,16 +253,16 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
<button
onClick={handleFinish}
disabled={isSubmitting}
className="flex items-center gap-2 px-6 py-3 bg-green-600 hover:bg-green-700 disabled:bg-green-400 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors duration-200"
className="flex items-center gap-2 rounded-lg bg-green-600 px-6 py-3 font-medium text-white transition-colors duration-200 hover:bg-green-700 disabled:cursor-not-allowed disabled:bg-green-400"
>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin" />
Completing...
</>
) : (
<>
<Check className="w-4 h-4" />
<Check className="h-4 w-4" />
Complete Setup
</>
)}

View File

@@ -30,17 +30,17 @@ export default function AgentConnectionCard({
: status.error || 'Not connected';
return (
<div className={`border rounded-lg p-4 transition-colors ${containerClassName}`}>
<div className={`rounded-lg border p-4 transition-colors ${containerClassName}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${iconContainerClassName}`}>
<SessionProviderLogo provider={provider} className="w-5 h-5" />
<div className={`flex h-10 w-10 items-center justify-center rounded-full ${iconContainerClassName}`}>
<SessionProviderLogo provider={provider} className="h-5 w-5" />
</div>
<div>
<div className="font-medium text-foreground flex items-center gap-2">
<div className="flex items-center gap-2 font-medium text-foreground">
{title}
{status.authenticated && <Check className="w-4 h-4 text-green-500" />}
{status.authenticated && <Check className="h-4 w-4 text-green-500" />}
</div>
<div className="text-xs text-muted-foreground">{statusText}</div>
</div>
@@ -49,7 +49,7 @@ export default function AgentConnectionCard({
{!status.authenticated && !status.loading && (
<button
onClick={onLogin}
className={`${loginButtonClassName} text-white text-sm font-medium py-2 px-4 rounded-lg transition-colors`}
className={`${loginButtonClassName} rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors`}
>
Login
</button>

View File

@@ -1,5 +1,5 @@
import AgentConnectionCard from './AgentConnectionCard';
import type { CliProvider, ProviderStatusMap } from '../types';
import AgentConnectionCard from './AgentConnectionCard';
type AgentConnectionsStepProps = {
providerStatuses: ProviderStatusMap;
@@ -43,8 +43,8 @@ export default function AgentConnectionsStep({
}: AgentConnectionsStepProps) {
return (
<div className="space-y-6">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold text-foreground mb-2">Connect Your AI Agents</h2>
<div className="mb-6 text-center">
<h2 className="mb-2 text-2xl font-bold text-foreground">Connect Your AI Agents</h2>
<p className="text-muted-foreground">
Login to one or more AI coding assistants. All are optional.
</p>
@@ -65,7 +65,7 @@ export default function AgentConnectionsStep({
))}
</div>
<div className="text-center text-sm text-muted-foreground pt-2">
<div className="pt-2 text-center text-sm text-muted-foreground">
<p>You can configure these later in Settings.</p>
</div>
</div>

View File

@@ -17,11 +17,11 @@ export default function GitConfigurationStep({
}: GitConfigurationStepProps) {
return (
<div className="space-y-6">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
<GitBranch className="w-8 h-8 text-blue-600 dark:text-blue-400" />
<div className="mb-8 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30">
<GitBranch className="h-8 w-8 text-blue-600 dark:text-blue-400" />
</div>
<h2 className="text-2xl font-bold text-foreground mb-2">Git Configuration</h2>
<h2 className="mb-2 text-2xl font-bold text-foreground">Git Configuration</h2>
<p className="text-muted-foreground">
Configure your git identity to ensure proper attribution for commits.
</p>
@@ -29,8 +29,8 @@ export default function GitConfigurationStep({
<div className="space-y-4">
<div>
<label htmlFor="gitName" className="flex items-center gap-2 text-sm font-medium text-foreground mb-2">
<User className="w-4 h-4" />
<label htmlFor="gitName" className="mb-2 flex items-center gap-2 text-sm font-medium text-foreground">
<User className="h-4 w-4" />
Git Name <span className="text-red-500">*</span>
</label>
<input
@@ -38,7 +38,7 @@ export default function GitConfigurationStep({
id="gitName"
value={gitName}
onChange={(event) => onGitNameChange(event.target.value)}
className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
className="w-full rounded-lg border border-border bg-background px-4 py-3 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="John Doe"
required
disabled={isSubmitting}
@@ -47,8 +47,8 @@ export default function GitConfigurationStep({
</div>
<div>
<label htmlFor="gitEmail" className="flex items-center gap-2 text-sm font-medium text-foreground mb-2">
<Mail className="w-4 h-4" />
<label htmlFor="gitEmail" className="mb-2 flex items-center gap-2 text-sm font-medium text-foreground">
<Mail className="h-4 w-4" />
Git Email <span className="text-red-500">*</span>
</label>
<input
@@ -56,7 +56,7 @@ export default function GitConfigurationStep({
id="gitEmail"
value={gitEmail}
onChange={(event) => onGitEmailChange(event.target.value)}
className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
className="w-full rounded-lg border border-border bg-background px-4 py-3 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="john@example.com"
required
disabled={isSubmitting}

View File

@@ -20,17 +20,17 @@ export default function OnboardingStepProgress({ currentStep }: OnboardingStepPr
return (
<div key={step.title} className="contents">
<div className="flex flex-col items-center flex-1">
<div className="flex flex-1 flex-col items-center">
<div
className={`w-12 h-12 rounded-full flex items-center justify-center border-2 transition-colors duration-200 ${
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 transition-colors duration-200 ${
isCompleted
? 'bg-green-500 border-green-500 text-white'
? 'border-green-500 bg-green-500 text-white'
: isActive
? 'bg-blue-600 border-blue-600 text-white'
: 'bg-background border-border text-muted-foreground'
? 'border-blue-600 bg-blue-600 text-white'
: 'border-border bg-background text-muted-foreground'
}`}
>
{isCompleted ? <Check className="w-6 h-6" /> : <Icon className="w-6 h-6" />}
{isCompleted ? <Check className="h-6 w-6" /> : <Icon className="h-6 w-6" />}
</div>
<div className="mt-2 text-center">
@@ -42,7 +42,7 @@ export default function OnboardingStepProgress({ currentStep }: OnboardingStepPr
</div>
{index < onboardingSteps.length - 1 && (
<div className={`flex-1 h-0.5 mx-2 transition-colors duration-200 ${isCompleted ? 'bg-green-500' : 'bg-border'}`} />
<div className={`mx-2 h-0.5 flex-1 transition-colors duration-200 ${isCompleted ? 'bg-green-500' : 'bg-border'}`} />
)}
</div>
);

View File

@@ -17,12 +17,12 @@ export default function GenerateTasksModal({
}
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[300] p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
<div className="w-full max-w-md rounded-lg border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center justify-between border-b border-gray-200 p-6 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-purple-100 dark:bg-purple-900/50 rounded-lg flex items-center justify-center">
<Sparkles className="w-4 h-4 text-purple-600 dark:text-purple-400" />
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/50">
<Sparkles className="h-4 w-4 text-purple-600 dark:text-purple-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Generate Tasks from PRD
@@ -30,35 +30,35 @@ export default function GenerateTasksModal({
</div>
<button
onClick={onClose}
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
>
<X className="w-5 h-5" />
<X className="h-5 w-5" />
</button>
</div>
<div className="p-6 space-y-4">
<div className="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-4 border border-purple-200 dark:border-purple-800">
<h4 className="font-semibold text-purple-900 dark:text-purple-100 mb-2">
<div className="space-y-4 p-6">
<div className="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-800 dark:bg-purple-900/20">
<h4 className="mb-2 font-semibold text-purple-900 dark:text-purple-100">
Ask Claude Code directly
</h4>
<p className="text-sm text-purple-800 dark:text-purple-200 mb-3">
<p className="mb-3 text-sm text-purple-800 dark:text-purple-200">
Save this PRD, then ask Claude Code in chat to parse the file and create your initial tasks.
</p>
<div className="bg-white dark:bg-gray-800 rounded border border-purple-200 dark:border-purple-700 p-3">
<p className="text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Example prompt</p>
<p className="text-xs text-gray-900 dark:text-white font-mono">
<div className="rounded border border-purple-200 bg-white p-3 dark:border-purple-700 dark:bg-gray-800">
<p className="mb-1 text-xs font-medium text-gray-600 dark:text-gray-400">Example prompt</p>
<p className="font-mono text-xs text-gray-900 dark:text-white">
I have a PRD at .taskmaster/docs/{fileName}. Parse it and create the initial tasks.
</p>
</div>
</div>
<div className="text-center pt-4 border-t border-gray-200 dark:border-gray-700">
<div className="border-t border-gray-200 pt-4 text-center dark:border-gray-700">
<a
href={PRD_DOCS_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-block text-sm text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 underline font-medium"
className="inline-block text-sm font-medium text-purple-600 underline hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300"
>
View TaskMaster documentation
</a>
@@ -66,7 +66,7 @@ export default function GenerateTasksModal({
<button
onClick={onClose}
className="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
className="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
>
Got it
</button>

View File

@@ -23,16 +23,16 @@ export default function OverwriteConfirmModal({
<div className="fixed inset-0 z-[300] flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black/50" onClick={onCancel} />
<div className="relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full border border-gray-200 dark:border-gray-700">
<div className="relative w-full max-w-md rounded-lg border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800">
<div className="p-6">
<div className="flex items-center mb-4">
<div className="p-2 rounded-full mr-3 bg-yellow-100 dark:bg-yellow-900">
<AlertTriangle className="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
<div className="mb-4 flex items-center">
<div className="mr-3 rounded-full bg-yellow-100 p-2 dark:bg-yellow-900">
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">File Already Exists</h3>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-6">
<p className="mb-6 text-sm text-gray-600 dark:text-gray-400">
A PRD named "{fileName}" already exists. Do you want to overwrite it?
</p>
@@ -40,16 +40,16 @@ export default function OverwriteConfirmModal({
<button
onClick={onCancel}
disabled={saving}
className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
>
Cancel
</button>
<button
onClick={onConfirm}
disabled={saving}
className="px-4 py-2 text-sm text-white bg-yellow-600 hover:bg-yellow-700 rounded-md flex items-center gap-2 transition-colors disabled:opacity-50"
className="flex items-center gap-2 rounded-md bg-yellow-600 px-4 py-2 text-sm text-white transition-colors hover:bg-yellow-700 disabled:opacity-50"
>
<Save className="w-4 h-4" />
<Save className="h-4 w-4" />
<span>{saving ? 'Saving...' : 'Overwrite'}</span>
</button>
</div>

View File

@@ -27,7 +27,7 @@ export default function PrdEditorBody({
if (previewMode) {
return (
<div className="h-full overflow-y-auto p-6 prose prose-gray dark:prose-invert max-w-none">
<div className="prose prose-gray h-full max-w-none overflow-y-auto p-6 dark:prose-invert">
<MarkdownPreview content={content} />
</div>
);

View File

@@ -22,7 +22,7 @@ export default function PrdEditorFooter({ content }: PrdEditorFooterProps) {
const stats = useMemo(() => getContentStats(content), [content]);
return (
<div className="flex items-center justify-between p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
<div className="flex flex-shrink-0 items-center justify-between border-t border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<span>Lines: {stats.lines}</span>
<span>Characters: {stats.characters}</span>

View File

@@ -82,36 +82,36 @@ export default function PrdEditorHeader({
const fileNameInputRef = useRef<HTMLInputElement | null>(null);
return (
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 min-w-0">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="w-8 h-8 bg-purple-600 rounded flex items-center justify-center flex-shrink-0">
<FileText className="w-4 h-4 text-white" />
<div className="flex min-w-0 flex-shrink-0 items-center justify-between border-b border-gray-200 p-4 dark:border-gray-700">
<div className="flex min-w-0 flex-1 items-center gap-3">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded bg-purple-600">
<FileText className="h-4 w-4 text-white" />
</div>
<div className="min-w-0 flex-1">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 min-w-0">
<div className="flex items-center gap-1 min-w-0 flex-1">
<div className="flex items-center min-w-0 flex-1 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-md px-3 py-2 focus-within:ring-2 focus-within:ring-purple-500 focus-within:border-purple-500 dark:focus-within:ring-purple-400 dark:focus-within:border-purple-400">
<div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center">
<div className="flex min-w-0 flex-1 items-center gap-1">
<div className="flex min-w-0 flex-1 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2 focus-within:border-purple-500 focus-within:ring-2 focus-within:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:focus-within:border-purple-400 dark:focus-within:ring-purple-400">
<input
ref={fileNameInputRef}
type="text"
value={fileName}
onChange={(event) => onFileNameChange(event.target.value)}
className="font-medium text-gray-900 dark:text-white bg-transparent border-none outline-none min-w-0 flex-1 text-base sm:text-sm placeholder-gray-400 dark:placeholder-gray-500"
className="min-w-0 flex-1 border-none bg-transparent text-base font-medium text-gray-900 placeholder-gray-400 outline-none dark:text-white dark:placeholder-gray-500 sm:text-sm"
placeholder="Enter PRD filename"
maxLength={100}
/>
<span className="text-sm sm:text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap ml-1">
<span className="ml-1 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 sm:text-xs">
.txt
</span>
</div>
<button
onClick={() => fileNameInputRef.current?.focus()}
className="p-1 text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
className="p-1 text-gray-400 transition-colors hover:text-purple-600 dark:hover:text-purple-400"
title="Focus filename input"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -122,36 +122,36 @@ export default function PrdEditorHeader({
</button>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className="text-xs bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 px-2 py-1 rounded whitespace-nowrap">
<div className="flex flex-shrink-0 items-center gap-2">
<span className="whitespace-nowrap rounded bg-purple-100 px-2 py-1 text-xs text-purple-600 dark:bg-purple-900 dark:text-purple-300">
PRD
</span>
{isNewFile && (
<span className="text-xs bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-300 px-2 py-1 rounded whitespace-nowrap">
<span className="whitespace-nowrap rounded bg-green-100 px-2 py-1 text-xs text-green-600 dark:bg-green-900 dark:text-green-300">
New
</span>
)}
</div>
</div>
<p className="text-xs sm:text-sm text-gray-500 dark:text-gray-400 truncate mt-1">
<p className="mt-1 truncate text-xs text-gray-500 dark:text-gray-400 sm:text-sm">
Product Requirements Document
</p>
</div>
</div>
<div className="flex items-center gap-1 md:gap-2 flex-shrink-0">
<div className="flex flex-shrink-0 items-center gap-1 md:gap-2">
<HeaderIconButton
title={previewMode ? 'Switch to edit mode' : 'Preview markdown'}
onClick={onTogglePreview}
icon={<Eye className="w-5 h-5 md:w-4 md:h-4" />}
icon={<Eye className="h-5 w-5 md:h-4 md:w-4" />}
active={previewMode}
/>
<HeaderIconButton
title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
onClick={onToggleWordWrap}
icon={<span className="text-sm md:text-xs font-mono font-bold">WRAP</span>}
icon={<span className="font-mono text-sm font-bold md:text-xs">WRAP</span>}
active={wordWrap}
/>
@@ -160,9 +160,9 @@ export default function PrdEditorHeader({
onClick={onToggleTheme}
icon={
isDarkMode ? (
<Sun className="w-5 h-5 md:w-4 md:h-4" />
<Sun className="h-5 w-5 md:h-4 md:w-4" />
) : (
<Moon className="w-5 h-5 md:w-4 md:h-4" />
<Moon className="h-5 w-5 md:h-4 md:w-4" />
)
}
/>
@@ -170,7 +170,7 @@ export default function PrdEditorHeader({
<HeaderIconButton
title="Download PRD"
onClick={onDownload}
icon={<Download className="w-5 h-5 md:w-4 md:h-4" />}
icon={<Download className="h-5 w-5 md:h-4 md:w-4" />}
/>
<button
@@ -182,7 +182,7 @@ export default function PrdEditorHeader({
)}
title="Generate tasks from PRD content"
>
<Sparkles className="w-4 h-4" />
<Sparkles className="h-4 w-4" />
<span className="hidden md:inline">Generate Tasks</span>
</button>
@@ -196,14 +196,14 @@ export default function PrdEditorHeader({
>
{saveSuccess ? (
<>
<svg className="w-5 h-5 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="h-5 w-5 md:h-4 md:w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="hidden sm:inline">Saved!</span>
</>
) : (
<>
<Save className="w-5 h-5 md:w-4 md:h-4" />
<Save className="h-5 w-5 md:h-4 md:w-4" />
<span className="hidden sm:inline">{saving ? 'Saving...' : 'Save PRD'}</span>
</>
)}
@@ -211,16 +211,16 @@ export default function PrdEditorHeader({
<button
onClick={onToggleFullscreen}
className="hidden md:flex p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 items-center justify-center"
className="hidden items-center justify-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white md:flex"
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
>
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
{isFullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
</button>
<HeaderIconButton
title="Close"
onClick={onClose}
icon={<X className="w-6 h-6 md:w-4 md:h-4" />}
icon={<X className="h-6 w-6 md:h-4 md:w-4" />}
/>
</div>
</div>

View File

@@ -1,9 +1,9 @@
export default function PrdEditorLoadingState() {
return (
<div className="fixed inset-0 z-[200] md:bg-black/50 md:flex md:items-center md:justify-center">
<div className="w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center bg-white dark:bg-gray-900">
<div className="fixed inset-0 z-[200] md:flex md:items-center md:justify-center md:bg-black/50">
<div className="flex h-full w-full items-center justify-center bg-white p-8 dark:bg-gray-900 md:h-auto md:w-auto md:rounded-lg">
<div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" />
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-blue-600" />
<span className="text-gray-900 dark:text-white">Loading PRD...</span>
</div>
</div>

View File

@@ -65,7 +65,7 @@ export default function PrdEditorWorkspace({
)}
>
{loadError && (
<div className="px-4 py-3 border-b border-yellow-200 dark:border-yellow-800 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-200 text-sm">
<div className="border-b border-yellow-200 bg-yellow-50 px-4 py-3 text-sm text-yellow-800 dark:border-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-200">
{loadError}
</div>
)}

View File

@@ -147,12 +147,12 @@ export default function ProjectCreationWizard({
);
return (
<div className="fixed top-0 left-0 right-0 bottom-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[60] p-0 sm:p-4">
<div className="bg-white dark:bg-gray-800 rounded-none sm:rounded-lg shadow-xl w-full h-full sm:h-auto sm:max-w-2xl border-0 sm:border border-gray-200 dark:border-gray-700 overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<div className="fixed bottom-0 left-0 right-0 top-0 z-[60] flex items-center justify-center bg-black/50 p-0 backdrop-blur-sm sm:p-4">
<div className="h-full w-full overflow-y-auto rounded-none border-0 border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800 sm:h-auto sm:max-w-2xl sm:rounded-lg sm:border">
<div className="flex items-center justify-between border-b border-gray-200 p-6 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center">
<FolderPlus className="w-4 h-4 text-blue-600 dark:text-blue-400" />
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/50">
<FolderPlus className="h-4 w-4 text-blue-600 dark:text-blue-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{t('projectWizard.title')}
@@ -160,16 +160,16 @@ export default function ProjectCreationWizard({
</div>
<button
onClick={onClose}
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
disabled={isCreating}
>
<X className="w-5 h-5" />
<X className="h-5 w-5" />
</button>
</div>
<WizardProgress step={step} />
<div className="p-6 space-y-6 min-h-[300px]">
<div className="min-h-[300px] space-y-6 p-6">
{error && <ErrorBanner message={error} />}
{step === 1 && (

View File

@@ -6,8 +6,8 @@ type ErrorBannerProps = {
export default function ErrorBanner({ message }: ErrorBannerProps) {
return (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
<div className="flex items-start gap-3 rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20">
<AlertCircle className="mt-0.5 h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-400" />
<p className="text-sm text-red-800 dark:text-red-200">{message}</p>
</div>
);

View File

@@ -97,12 +97,12 @@ export default function FolderBrowserModal({
}
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[70] p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] border border-gray-200 dark:border-gray-700 flex flex-col">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
<div className="flex max-h-[80vh] w-full max-w-2xl flex-col rounded-lg border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center justify-between border-b border-gray-200 p-4 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center">
<FolderOpen className="w-4 h-4 text-blue-600 dark:text-blue-400" />
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/50">
<FolderOpen className="h-4 w-4 text-blue-600 dark:text-blue-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Select Folder</h3>
</div>
@@ -110,37 +110,37 @@ export default function FolderBrowserModal({
<div className="flex items-center gap-2">
<button
onClick={() => setShowHiddenFolders((previous) => !previous)}
className={`p-2 rounded-md transition-colors ${
className={`rounded-md p-2 transition-colors ${
showHiddenFolders
? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30'
: 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
: 'text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300'
}`}
title={showHiddenFolders ? 'Hide hidden folders' : 'Show hidden folders'}
>
{showHiddenFolders ? <Eye className="w-5 h-5" /> : <EyeOff className="w-5 h-5" />}
{showHiddenFolders ? <Eye className="h-5 w-5" /> : <EyeOff className="h-5 w-5" />}
</button>
<button
onClick={() => setShowNewFolderInput((previous) => !previous)}
className={`p-2 rounded-md transition-colors ${
className={`rounded-md p-2 transition-colors ${
showNewFolderInput
? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30'
: 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
: 'text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300'
}`}
title="Create new folder"
>
<Plus className="w-5 h-5" />
<Plus className="h-5 w-5" />
</button>
<button
onClick={handleClose}
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
>
<X className="w-5 h-5" />
<X className="h-5 w-5" />
</button>
</div>
</div>
{showNewFolderInput && (
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700 bg-blue-50 dark:bg-blue-900/20">
<div className="border-b border-gray-200 bg-blue-50 px-4 py-3 dark:border-gray-700 dark:bg-blue-900/20">
<div className="flex items-center gap-2">
<Input
type="text"
@@ -163,7 +163,7 @@ export default function FolderBrowserModal({
onClick={handleCreateFolder}
disabled={!newFolderName.trim() || creatingFolder}
>
{creatingFolder ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Create'}
{creatingFolder ? <Loader2 className="h-4 w-4 animate-spin" /> : 'Create'}
</Button>
<Button size="sm" variant="ghost" onClick={resetNewFolderState}>
Cancel
@@ -181,22 +181,22 @@ export default function FolderBrowserModal({
<div className="flex-1 overflow-y-auto p-4">
{loadingFolders ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="w-6 h-6 animate-spin text-gray-400" />
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
) : (
<div className="space-y-1">
{parentPath && (
<button
onClick={() => loadFolders(parentPath)}
className="w-full px-4 py-3 text-left hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg flex items-center gap-3"
className="flex w-full items-center gap-3 rounded-lg px-4 py-3 text-left hover:bg-gray-100 dark:hover:bg-gray-700"
>
<FolderOpen className="w-5 h-5 text-gray-400" />
<FolderOpen className="h-5 w-5 text-gray-400" />
<span className="font-medium text-gray-700 dark:text-gray-300">..</span>
</button>
)}
{visibleFolders.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<div className="py-8 text-center text-gray-500 dark:text-gray-400">
No subfolders found
</div>
) : (
@@ -204,9 +204,9 @@ export default function FolderBrowserModal({
<div key={folder.path} className="flex items-center gap-2">
<button
onClick={() => loadFolders(folder.path)}
className="flex-1 px-4 py-3 text-left hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg flex items-center gap-3"
className="flex flex-1 items-center gap-3 rounded-lg px-4 py-3 text-left hover:bg-gray-100 dark:hover:bg-gray-700"
>
<FolderPlus className="w-5 h-5 text-blue-500" />
<FolderPlus className="h-5 w-5 text-blue-500" />
<span className="font-medium text-gray-900 dark:text-white">
{folder.name}
</span>
@@ -215,7 +215,7 @@ export default function FolderBrowserModal({
variant="ghost"
size="sm"
onClick={() => onFolderSelected(folder.path, autoAdvanceOnSelect)}
className="text-xs px-3"
className="px-3 text-xs"
>
Select
</Button>
@@ -227,9 +227,9 @@ export default function FolderBrowserModal({
</div>
<div className="border-t border-gray-200 dark:border-gray-700">
<div className="px-4 py-3 bg-gray-50 dark:bg-gray-900/50 flex items-center gap-2">
<div className="flex items-center gap-2 bg-gray-50 px-4 py-3 dark:bg-gray-900/50">
<span className="text-sm text-gray-600 dark:text-gray-400">Path:</span>
<code className="text-sm font-mono text-gray-900 dark:text-white flex-1 truncate">
<code className="flex-1 truncate font-mono text-sm text-gray-900 dark:text-white">
{currentPath}
</code>
</div>

View File

@@ -38,11 +38,11 @@ export default function GithubAuthenticationCard({
const { t } = useTranslation();
return (
<div className="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
<div className="flex items-start gap-3 mb-4">
<Key className="w-5 h-5 text-gray-600 dark:text-gray-400 flex-shrink-0 mt-0.5" />
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50">
<div className="mb-4 flex items-start gap-3">
<Key className="mt-0.5 h-5 w-5 flex-shrink-0 text-gray-600 dark:text-gray-400" />
<div className="flex-1">
<h5 className="font-medium text-gray-900 dark:text-white mb-1">
<h5 className="mb-1 font-medium text-gray-900 dark:text-white">
{t('projectWizard.step2.githubAuth')}
</h5>
<p className="text-sm text-gray-600 dark:text-gray-400">
@@ -53,18 +53,18 @@ export default function GithubAuthenticationCard({
{loadingTokens && (
<div className="flex items-center gap-2 text-sm text-gray-500">
<Loader2 className="w-4 h-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin" />
{t('projectWizard.step2.loadingTokens')}
</div>
)}
{!loadingTokens && tokenLoadError && (
<p className="text-sm text-red-600 dark:text-red-400 mb-3">{tokenLoadError}</p>
<p className="mb-3 text-sm text-red-600 dark:text-red-400">{tokenLoadError}</p>
)}
{!loadingTokens && availableTokens.length > 0 && (
<>
<div className="grid grid-cols-3 gap-2 mb-4">
<div className="mb-4 grid grid-cols-3 gap-2">
<button
onClick={() => onTokenModeChange('stored')}
className={getModeClassName(tokenMode, 'stored')}
@@ -91,13 +91,13 @@ export default function GithubAuthenticationCard({
{tokenMode === 'stored' ? (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{t('projectWizard.step2.selectToken')}
</label>
<select
value={selectedGithubToken}
onChange={(event) => onSelectedGithubTokenChange(event.target.value)}
className="w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-sm"
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-800"
>
<option value="">{t('projectWizard.step2.selectTokenPlaceholder')}</option>
{availableTokens.map((token) => (
@@ -109,7 +109,7 @@ export default function GithubAuthenticationCard({
</div>
) : tokenMode === 'new' ? (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{t('projectWizard.step2.newToken')}
</label>
<Input
@@ -129,14 +129,14 @@ export default function GithubAuthenticationCard({
{!loadingTokens && availableTokens.length === 0 && (
<div className="space-y-4">
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3 border border-blue-200 dark:border-blue-800">
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
<p className="text-sm text-blue-800 dark:text-blue-200">
{t('projectWizard.step2.publicRepoInfo')}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{t('projectWizard.step2.optionalTokenPublic')}
</label>
<Input

View File

@@ -1,9 +1,9 @@
import { useTranslation } from 'react-i18next';
import { Input } from '../../../shared/view/ui';
import GithubAuthenticationCard from './GithubAuthenticationCard';
import WorkspacePathField from './WorkspacePathField';
import { shouldShowGithubAuthentication } from '../utils/pathUtils';
import type { GithubTokenCredential, TokenMode, WorkspaceType } from '../types';
import GithubAuthenticationCard from './GithubAuthenticationCard';
import WorkspacePathField from './WorkspacePathField';
type StepConfigurationProps = {
workspaceType: WorkspaceType;
@@ -48,7 +48,7 @@ export default function StepConfiguration({
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{workspaceType === 'existing'
? t('projectWizard.step2.existingPath')
: t('projectWizard.step2.newPath')}
@@ -72,7 +72,7 @@ export default function StepConfiguration({
{workspaceType === 'new' && (
<>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{t('projectWizard.step2.githubUrl')}
</label>
<Input

View File

@@ -36,8 +36,8 @@ export default function StepReview({
return (
<div className="space-y-4">
<div className="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
<h4 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/50">
<h4 className="mb-3 text-sm font-semibold text-gray-900 dark:text-white">
{t('projectWizard.step3.reviewConfig')}
</h4>
@@ -55,7 +55,7 @@ export default function StepReview({
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">{t('projectWizard.step3.path')}</span>
<span className="font-mono text-xs text-gray-900 dark:text-white break-all">
<span className="break-all font-mono text-xs text-gray-900 dark:text-white">
{formState.workspacePath}
</span>
</div>
@@ -66,7 +66,7 @@ export default function StepReview({
<span className="text-gray-600 dark:text-gray-400">
{t('projectWizard.step3.cloneFrom')}
</span>
<span className="font-mono text-xs text-gray-900 dark:text-white break-all">
<span className="break-all font-mono text-xs text-gray-900 dark:text-white">
{formState.githubUrl}
</span>
</div>
@@ -82,13 +82,13 @@ export default function StepReview({
</div>
</div>
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-800 dark:bg-blue-900/20">
{isCreating && cloneProgress ? (
<div className="space-y-2">
<p className="text-sm font-medium text-blue-800 dark:text-blue-200">
{t('projectWizard.step3.cloningRepository', { defaultValue: 'Cloning repository...' })}
</p>
<code className="block text-xs font-mono text-blue-700 dark:text-blue-300 whitespace-pre-wrap break-all">
<code className="block whitespace-pre-wrap break-all font-mono text-xs text-blue-700 dark:text-blue-300">
{cloneProgress}
</code>
</div>

View File

@@ -15,25 +15,25 @@ export default function StepTypeSelection({
return (
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
<h4 className="mb-3 text-sm font-medium text-gray-700 dark:text-gray-300">
{t('projectWizard.step1.question')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<button
onClick={() => onWorkspaceTypeChange('existing')}
className={`p-4 border-2 rounded-lg text-left transition-all ${
className={`rounded-lg border-2 p-4 text-left transition-all ${
workspaceType === 'existing'
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
: 'border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600'
}`}
>
<div className="flex items-start gap-3">
<div className="w-10 h-10 bg-green-100 dark:bg-green-900/50 rounded-lg flex items-center justify-center flex-shrink-0">
<FolderPlus className="w-5 h-5 text-green-600 dark:text-green-400" />
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900/50">
<FolderPlus className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div className="flex-1">
<h5 className="font-semibold text-gray-900 dark:text-white mb-1">
<h5 className="mb-1 font-semibold text-gray-900 dark:text-white">
{t('projectWizard.step1.existing.title')}
</h5>
<p className="text-sm text-gray-600 dark:text-gray-400">
@@ -45,18 +45,18 @@ export default function StepTypeSelection({
<button
onClick={() => onWorkspaceTypeChange('new')}
className={`p-4 border-2 rounded-lg text-left transition-all ${
className={`rounded-lg border-2 p-4 text-left transition-all ${
workspaceType === 'new'
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
: 'border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600'
}`}
>
<div className="flex items-start gap-3">
<div className="w-10 h-10 bg-purple-100 dark:bg-purple-900/50 rounded-lg flex items-center justify-center flex-shrink-0">
<GitBranch className="w-5 h-5 text-purple-600 dark:text-purple-400" />
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/50">
<GitBranch className="h-5 w-5 text-purple-600 dark:text-purple-400" />
</div>
<div className="flex-1">
<h5 className="font-semibold text-gray-900 dark:text-white mb-1">
<h5 className="mb-1 font-semibold text-gray-900 dark:text-white">
{t('projectWizard.step1.new.title')}
</h5>
<p className="text-sm text-gray-600 dark:text-gray-400">

View File

@@ -25,13 +25,13 @@ export default function WizardFooter({
const { t } = useTranslation();
return (
<div className="flex items-center justify-between p-6 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between border-t border-gray-200 p-6 dark:border-gray-700">
<Button variant="outline" onClick={step === 1 ? onClose : onBack} disabled={isCreating}>
{step === 1 ? (
t('projectWizard.buttons.cancel')
) : (
<>
<ChevronLeft className="w-4 h-4 mr-1" />
<ChevronLeft className="mr-1 h-4 w-4" />
{t('projectWizard.buttons.back')}
</>
)}
@@ -40,20 +40,20 @@ export default function WizardFooter({
<Button onClick={step === 3 ? onCreate : onNext} disabled={isCreating}>
{isCreating ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{isCloneWorkflow
? t('projectWizard.buttons.cloning', { defaultValue: 'Cloning...' })
: t('projectWizard.buttons.creating')}
</>
) : step === 3 ? (
<>
<Check className="w-4 h-4 mr-1" />
<Check className="mr-1 h-4 w-4" />
{t('projectWizard.buttons.createProject')}
</>
) : (
<>
{t('projectWizard.buttons.next')}
<ChevronRight className="w-4 h-4 ml-1" />
<ChevronRight className="ml-1 h-4 w-4" />
</>
)}
</Button>

View File

@@ -12,23 +12,23 @@ export default function WizardProgress({ step }: WizardProgressProps) {
const steps: WizardStep[] = [1, 2, 3];
return (
<div className="px-6 pt-4 pb-2">
<div className="px-6 pb-2 pt-4">
<div className="flex items-center justify-between">
{steps.map((currentStep) => (
<Fragment key={currentStep}>
<div className="flex items-center gap-2">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center font-medium text-sm ${
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${
currentStep < step
? 'bg-green-500 text-white'
: currentStep === step
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-500'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700'
}`}
>
{currentStep < step ? <Check className="w-4 h-4" /> : currentStep}
{currentStep < step ? <Check className="h-4 w-4" /> : currentStep}
</div>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 hidden sm:inline">
<span className="hidden text-sm font-medium text-gray-700 dark:text-gray-300 sm:inline">
{currentStep === 1
? t('projectWizard.steps.type')
: currentStep === 2
@@ -39,7 +39,7 @@ export default function WizardProgress({ step }: WizardProgressProps) {
{currentStep < 3 && (
<div
className={`flex-1 h-1 mx-2 rounded ${
className={`mx-2 h-1 flex-1 rounded ${
currentStep < step ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-700'
}`}
/>

View File

@@ -1,10 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { FolderOpen } from 'lucide-react';
import { Button, Input } from '../../../shared/view/ui';
import FolderBrowserModal from './FolderBrowserModal';
import { browseFilesystemFolders } from '../data/workspaceApi';
import { getSuggestionRootPath } from '../utils/pathUtils';
import type { FolderSuggestion, WorkspaceType } from '../types';
import FolderBrowserModal from './FolderBrowserModal';
type WorkspacePathFieldProps = {
workspaceType: WorkspaceType;
@@ -83,7 +83,7 @@ export default function WorkspacePathField({
return (
<>
<div className="relative flex gap-2">
<div className="flex-1 relative">
<div className="relative flex-1">
<Input
type="text"
value={value}
@@ -98,12 +98,12 @@ export default function WorkspacePathField({
/>
{showPathDropdown && pathSuggestions.length > 0 && (
<div className="absolute z-10 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-60 overflow-y-auto">
<div className="absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800">
{pathSuggestions.map((suggestion) => (
<button
key={suggestion.path}
onClick={() => handleSuggestionSelect(suggestion)}
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
className="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
>
<div className="font-medium text-gray-900 dark:text-white">{suggestion.name}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">{suggestion.path}</div>
@@ -121,7 +121,7 @@ export default function WorkspacePathField({
title="Browse folders"
disabled={disabled}
>
<FolderOpen className="w-4 h-4" />
<FolderOpen className="h-4 w-4" />
</Button>
</div>

View File

@@ -100,58 +100,58 @@ export default function ProviderLoginModal({
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999] max-md:items-stretch max-md:justify-stretch">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-4xl h-3/4 flex flex-col md:max-w-4xl md:h-3/4 md:rounded-lg md:m-4 max-md:max-w-none max-md:h-full max-md:rounded-none max-md:m-0">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-50 max-md:items-stretch max-md:justify-stretch">
<div className="flex h-3/4 w-full max-w-4xl flex-col rounded-lg bg-white shadow-xl dark:bg-gray-800 max-md:m-0 max-md:h-full max-md:max-w-none max-md:rounded-none md:m-4 md:h-3/4 md:max-w-4xl md:rounded-lg">
<div className="flex items-center justify-between border-b border-gray-200 p-4 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{title}</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
className="text-gray-400 transition-colors hover:text-gray-600 dark:hover:text-gray-300"
aria-label="Close login modal"
>
<X className="w-6 h-6" />
<X className="h-6 w-6" />
</button>
</div>
<div className="flex-1 overflow-hidden">
{provider === 'gemini' ? (
<div className="flex flex-col items-center justify-center h-full p-8 text-center bg-gray-50 dark:bg-gray-900/50">
<div className="w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center mb-6">
<KeyRound className="w-8 h-8 text-blue-600 dark:text-blue-400" />
<div className="flex h-full flex-col items-center justify-center bg-gray-50 p-8 text-center dark:bg-gray-900/50">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30">
<KeyRound className="h-8 w-8 text-blue-600 dark:text-blue-400" />
</div>
<h4 className="text-xl font-medium text-gray-900 dark:text-white mb-3">Setup Gemini API Access</h4>
<h4 className="mb-3 text-xl font-medium text-gray-900 dark:text-white">Setup Gemini API Access</h4>
<p className="text-gray-600 dark:text-gray-400 max-w-md mb-8">
<p className="mb-8 max-w-md text-gray-600 dark:text-gray-400">
The Gemini CLI requires an API key to function. Configure it in your terminal first.
</p>
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 max-w-lg w-full text-left shadow-sm">
<div className="w-full max-w-lg rounded-xl border border-gray-200 bg-white p-6 text-left shadow-sm dark:border-gray-700 dark:bg-gray-800">
<ol className="space-y-4">
<li className="flex gap-4">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400 flex items-center justify-center text-sm font-medium">
<div className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-sm font-medium text-blue-600 dark:bg-blue-900/50 dark:text-blue-400">
1
</div>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white mb-1">Get your API key</p>
<p className="mb-1 text-sm font-medium text-gray-900 dark:text-white">Get your API key</p>
<a
href="https://aistudio.google.com/app/apikey"
target="_blank"
rel="noreferrer"
className="text-sm text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1 inline-flex"
className="flex inline-flex items-center gap-1 text-sm text-blue-600 hover:underline dark:text-blue-400"
>
Google AI Studio <ExternalLink className="w-3 h-3" />
Google AI Studio <ExternalLink className="h-3 w-3" />
</a>
</div>
</li>
<li className="flex gap-4">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400 flex items-center justify-center text-sm font-medium">
<div className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-sm font-medium text-blue-600 dark:bg-blue-900/50 dark:text-blue-400">
2
</div>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white mb-1">Run configuration</p>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Open your terminal and run:</p>
<code className="block bg-gray-100 dark:bg-gray-900 px-3 py-2 rounded text-sm text-pink-600 dark:text-pink-400 font-mono">
<p className="mb-1 text-sm font-medium text-gray-900 dark:text-white">Run configuration</p>
<p className="mb-2 text-sm text-gray-600 dark:text-gray-400">Open your terminal and run:</p>
<code className="block rounded bg-gray-100 px-3 py-2 font-mono text-sm text-pink-600 dark:bg-gray-900 dark:text-pink-400">
gemini config set api_key YOUR_KEY
</code>
</div>
@@ -161,7 +161,7 @@ export default function ProviderLoginModal({
<button
onClick={onClose}
className="mt-8 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
className="mt-8 rounded-lg bg-blue-600 px-6 py-2.5 font-medium text-white transition-colors hover:bg-blue-700"
>
Done
</button>

View File

@@ -45,7 +45,7 @@ export default function QuickSettingsContent({
);
return (
<div className={`flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6 bg-background ${isMobile ? 'pb-mobile-nav' : ''}`}>
<div className={`flex-1 space-y-6 overflow-y-auto overflow-x-hidden bg-background p-4 ${isMobile ? 'pb-mobile-nav' : ''}`}>
<QuickSettingsSection title={t('quickSettings.sections.appearance')}>
<div className={SETTING_ROW_CLASS}>
<span className="flex items-center gap-2 text-sm text-gray-900 dark:text-white">
@@ -71,7 +71,7 @@ export default function QuickSettingsContent({
<QuickSettingsSection title={t('quickSettings.sections.inputSettings')}>
{renderToggleRows(INPUT_SETTING_TOGGLES)}
<p className="text-xs text-gray-500 dark:text-gray-400 ml-3">
<p className="ml-3 text-xs text-gray-500 dark:text-gray-400">
{t('quickSettings.sendByCtrlEnterDescription')}
</p>
</QuickSettingsSection>

Some files were not shown because too many files have changed in this diff Show More