From 8df5e68a001f6e6f7548c20924b1022a9a1486d9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:11:52 +0000 Subject: [PATCH 1/3] feat: Add Japanese translation This commit adds Japanese translation to the application. - A new `i18n.js` file has been created to store all the Japanese translation strings. - The following components have been modified to use the new translations: - `App.jsx` - `ChatInterface.jsx` - `LoginForm.jsx` - `SetupForm.jsx` - `Sidebar.jsx` - `ToolsSettings.jsx` --- src/App.jsx | 18 +- src/components/ChatInterface.jsx | 151 ++++++++-------- src/components/LoginForm.jsx | 20 ++- src/components/SetupForm.jsx | 28 +-- src/components/Sidebar.jsx | 154 ++++++++-------- src/components/ToolsSettings.jsx | 266 +++++++++++++-------------- src/lib/i18n.js | 298 +++++++++++++++++++++++++++++++ 7 files changed, 623 insertions(+), 312 deletions(-) create mode 100644 src/lib/i18n.js diff --git a/src/App.jsx b/src/App.jsx index 1cbd7eb8..8fc00cae 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -20,6 +20,7 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, useNavigate, useParams } from 'react-router-dom'; +import { translations } from './lib/i18n.js'; import Sidebar from './components/Sidebar'; import MainContent from './components/MainContent'; import MobileNav from './components/MobileNav'; @@ -38,6 +39,7 @@ import { api, authenticatedFetch } from './utils/api'; function AppContent() { const navigate = useNavigate(); const { sessionId } = useParams(); + const t = (key) => translations[key] || key; const { updateAvailable, latestVersion, currentVersion } = useVersionCheck('siteboon', 'claudecodeui'); const [showVersionModal, setShowVersionModal] = useState(false); @@ -474,8 +476,8 @@ function AppContent() {
A new version is ready
+{t("A new version is ready")}
{message.toolInput}
@@ -370,7 +371,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
return (
- {t("View input parameters")}
+ {t('viewInputParameters')}
{message.toolInput}
@@ -400,7 +401,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- 📄 {t("Creating new file:")}
+ 📄 {t('creatingNewFile')}
- {t("New File")}
+ {t('New File')}
{message.toolInput}
@@ -482,14 +483,14 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- {t("Updating Todo List")}
+ {t('updatingTodoList')}
{showRawParameters && (
- {t("View raw parameters")}
+ {t('viewRawParameters')}
{message.toolInput}
@@ -515,7 +516,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- {t("Running command")}
+ {t('runningCommand')}
@@ -523,7 +524,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
- {t("Terminal")}
+ {t('Terminal')}
$ {input.command}
@@ -537,7 +538,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
{showRawParameters && (
- {t("View raw parameters")}
+ {t('viewRawParameters')}
{message.toolInput}
@@ -561,7 +562,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
return (
- {t("Read")}{' '}
+ {t('Read')}{' '}
@@ -120,13 +207,13 @@ function MainContent({
- Choose Your Project
+ {t('chooseYourProject')}
- Select a project from the sidebar to start coding with Claude. Each project contains your chat sessions and file history.
+ {t('selectAProject')}
- 💡 Tip: {isMobile ? 'Tap the menu button above to access projects' : 'Create a new project by clicking the folder icon in the sidebar'}
+ 💡 {t('Tip')}: {isMobile ? t('tipMobile') : t('tipDesktop')}
@@ -169,7 +256,7 @@ function MainContent({
{activeTab === 'chat' && selectedSession ? (
- {selectedSession.__provider === 'cursor' ? (selectedSession.name || 'Untitled Session') : (selectedSession.summary || 'New Session')}
+ {selectedSession.__provider === 'cursor' ? (selectedSession.name || t('untitledSession')) : (selectedSession.summary || t('New Session'))}
{selectedProject.displayName} • {selectedSession.id}
@@ -178,7 +265,7 @@ function MainContent({
) : activeTab === 'chat' && !selectedSession ? (
- New Session
+ {t('New Session')}
{selectedProject.displayName}
@@ -187,7 +274,10 @@ function MainContent({
) : (
- {activeTab === 'files' ? 'Project Files' : activeTab === 'git' ? 'Source Control' : 'Project'}
+ {activeTab === 'files' ? t('Project Files') :
+ activeTab === 'git' ? t('Source Control') :
+ (activeTab === 'tasks' && shouldShowTasksTab) ? t('TaskMaster') :
+ t('Project')}
{selectedProject.displayName}
@@ -201,66 +291,93 @@ function MainContent({
{/* Modern Tab Navigation - Right Side */}
- setActiveTab('chat')}
- className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md ${
- activeTab === 'chat'
- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
- >
-
-
-
-
- Chat
-
-
- setActiveTab('shell')}
- className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
- activeTab === 'shell'
- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
- >
-
-
-
-
- Shell
-
-
- setActiveTab('files')}
- className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
- activeTab === 'files'
- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
- >
-
-
-
-
- Files
-
-
- setActiveTab('git')}
- className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
- activeTab === 'git'
- ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
- >
-
-
-
-
- Source Control
-
-
+
+ setActiveTab('chat')}
+ className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md ${
+ activeTab === 'chat'
+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
+ }`}
+ >
+
+
+
+
+ Chat
+
+
+
+
+ setActiveTab('shell')}
+ className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
+ activeTab === 'shell'
+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
+ }`}
+ >
+
+
+
+
+ Shell
+
+
+
+
+ setActiveTab('files')}
+ className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
+ activeTab === 'files'
+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
+ }`}
+ >
+
+
+
+
+ Files
+
+
+
+
+ setActiveTab('git')}
+ className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
+ activeTab === 'git'
+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
+ }`}
+ >
+
+
+
+
+ Source Control
+
+
+
+ {shouldShowTasksTab && (
+
+ setActiveTab('tasks')}
+ className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
+ activeTab === 'tasks'
+ ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200 dark:hover:bg-gray-700'
+ }`}
+ >
+
+
+
+
+ Tasks
+
+
+
+ )}
{/* setActiveTab('preview')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
@@ -302,6 +419,7 @@ function MainContent({
showRawParameters={showRawParameters}
autoScrollToBottom={autoScrollToBottom}
sendByCtrlEnter={sendByCtrlEnter}
+ onShowAllTasks={tasksEnabled ? () => setActiveTab('tasks') : null}
/>
@@ -318,6 +436,40 @@ function MainContent({
+ {shouldShowTasksTab && (
+
+
+ {
+ setSelectedPRD(prd);
+ setShowPRDEditor(true);
+ }}
+ existingPRDs={existingPRDs}
+ onRefreshPRDs={(showNotification = false) => {
+ // Reload existing PRDs
+ if (currentProject?.name) {
+ api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}`)
+ .then(response => response.ok ? response.json() : Promise.reject())
+ .then(data => {
+ setExistingPRDs(data.prdFiles || []);
+ if (showNotification) {
+ setPRDNotification('PRD saved successfully!');
+ setTimeout(() => setPRDNotification(null), 3000);
+ }
+ })
+ .catch(error => console.error('Failed to refresh PRDs:', error));
+ }
+ }}
+ />
+
+
+ )}
{/*
)}
+
+ {/* Task Detail Modal */}
+ {shouldShowTasksTab && showTaskDetail && selectedTask && (
+
+ )}
+ {/* PRD Editor Modal */}
+ {showPRDEditor && (
+ {
+ setShowPRDEditor(false);
+ setSelectedPRD(null);
+ }}
+ isNewFile={!selectedPRD?.isExisting}
+ file={{
+ name: selectedPRD?.name || 'prd.txt',
+ content: selectedPRD?.content || ''
+ }}
+ onSave={async () => {
+ setShowPRDEditor(false);
+ setSelectedPRD(null);
+
+ // Reload existing PRDs with notification
+ try {
+ const response = await api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}`);
+ if (response.ok) {
+ const data = await response.json();
+ setExistingPRDs(data.prdFiles || []);
+ setPRDNotification('PRD saved successfully!');
+ setTimeout(() => setPRDNotification(null), 3000);
+ }
+ } catch (error) {
+ console.error('Failed to refresh PRDs:', error);
+ }
+
+ refreshTasks?.();
+ }}
+ />
+ )}
+ {/* PRD Notification */}
+ {prdNotification && (
+
+
+
+
+
+ {prdNotification}
+
+
+ )}
);
}
diff --git a/src/components/MobileNav.jsx b/src/components/MobileNav.jsx
index 185a9f43..b985142c 100644
--- a/src/components/MobileNav.jsx
+++ b/src/components/MobileNav.jsx
@@ -1,7 +1,9 @@
import React from 'react';
-import { MessageSquare, Folder, Terminal, GitBranch, Globe } from 'lucide-react';
+import { MessageSquare, Folder, Terminal, GitBranch, Globe, CheckSquare } from 'lucide-react';
+import { useTasksSettings } from '../contexts/TasksSettingsContext';
function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
+ const { tasksEnabled } = useTasksSettings();
// Detect dark mode
const isDarkMode = document.documentElement.classList.contains('dark');
const navItems = [
@@ -24,7 +26,13 @@ function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
id: 'git',
icon: GitBranch,
onClick: () => setActiveTab('git')
- }
+ },
+ // Conditionally add tasks tab if enabled
+ ...(tasksEnabled ? [{
+ id: 'tasks',
+ icon: CheckSquare,
+ onClick: () => setActiveTab('tasks')
+ }] : [])
];
return (
diff --git a/src/components/NextTaskBanner.jsx b/src/components/NextTaskBanner.jsx
new file mode 100644
index 00000000..d4571ec6
--- /dev/null
+++ b/src/components/NextTaskBanner.jsx
@@ -0,0 +1,695 @@
+import React, { useState } from 'react';
+import { ArrowRight, List, Clock, Flag, CheckCircle, Circle, AlertCircle, Pause, ChevronDown, ChevronUp, Plus, FileText, Settings, X, Terminal, Eye, Play, Zap, Target } from 'lucide-react';
+import { cn } from '../lib/utils';
+import { useTaskMaster } from '../contexts/TaskMasterContext';
+import { api } from '../utils/api';
+import Shell from './Shell';
+import TaskDetail from './TaskDetail';
+
+const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
+ const { nextTask, tasks, currentProject, isLoadingTasks, projectTaskMaster, refreshTasks, refreshProjects } = useTaskMaster();
+ const [showDetails, setShowDetails] = useState(false);
+ const [showTaskOptions, setShowTaskOptions] = useState(false);
+ const [showCreateTaskModal, setShowCreateTaskModal] = useState(false);
+ const [showTemplateSelector, setShowTemplateSelector] = useState(false);
+ const [showCLI, setShowCLI] = useState(false);
+ const [showTaskDetail, setShowTaskDetail] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // Handler functions
+ const handleInitializeTaskMaster = async () => {
+ if (!currentProject) return;
+
+ setIsLoading(true);
+ try {
+ const response = await api.taskmaster.init(currentProject.name);
+ if (response.ok) {
+ await refreshProjects();
+ setShowTaskOptions(false);
+ } else {
+ const error = await response.json();
+ console.error('Failed to initialize TaskMaster:', error);
+ alert(`Failed to initialize TaskMaster: ${error.message}`);
+ }
+ } catch (error) {
+ console.error('Error initializing TaskMaster:', error);
+ alert('Error initializing TaskMaster. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleCreateManualTask = () => {
+ setShowCreateTaskModal(true);
+ setShowTaskOptions(false);
+ };
+
+ const handleParsePRD = () => {
+ setShowTemplateSelector(true);
+ setShowTaskOptions(false);
+ };
+
+ // Don't show if no project or still loading
+ if (!currentProject || isLoadingTasks) {
+ return null;
+ }
+
+ let bannerContent;
+
+ // Show setup message only if no tasks exist AND TaskMaster is not configured
+ if ((!tasks || tasks.length === 0) && !projectTaskMaster?.hasTaskmaster) {
+ bannerContent = (
+
+
+
+
+
+
+ TaskMaster AI is not configured
+
+
+
+
+
+
+ setShowTaskOptions(!showTaskOptions)}
+ className="text-xs px-2 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex items-center gap-1"
+ >
+
+ Initialize TaskMaster AI
+
+
+
+
+ {showTaskOptions && (
+
+ {!projectTaskMaster?.hasTaskmaster && (
+
+
+ 🎯 What is TaskMaster?
+
+
+ • AI-Powered Task Management: Break complex projects into manageable subtasks
+ • PRD Templates: Generate tasks from Product Requirements Documents
+ • Dependency Tracking: Understand task relationships and execution order
+ • Progress Visualization: Kanban boards and detailed task analytics
+ • CLI Integration: Use taskmaster commands for advanced workflows
+
+
+ )}
+
+ {!projectTaskMaster?.hasTaskmaster ? (
+ setShowCLI(true)}
+ >
+
+ Initialize TaskMaster
+
+ ) : (
+ <>
+
+ Add more tasks: Create additional tasks manually or generate them from a PRD template
+
+
+
+ Create a new task manually
+
+
+
+ {isLoading ? 'Parsing...' : 'Generate tasks from PRD template'}
+
+ >
+ )}
+
+
+ )}
+
+ );
+ } else if (nextTask) {
+ // Show next task if available
+ bannerContent = (
+
+
+
+
+
+
+
+ Task {nextTask.id}
+ {nextTask.priority === 'high' && (
+
+
+
+ )}
+ {nextTask.priority === 'medium' && (
+
+
+
+ )}
+ {nextTask.priority === 'low' && (
+
+
+
+ )}
+
+
+ {nextTask.title}
+
+
+
+
+ onStartTask?.()}
+ className="text-xs px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium transition-colors shadow-sm flex items-center gap-1"
+ >
+
+ Start Task
+
+ setShowTaskDetail(true)}
+ className="text-xs px-2 py-1.5 border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 rounded-md transition-colors flex items-center gap-1"
+ title="View task details"
+ >
+
+
+ {onShowAllTasks && (
+
+
+
+ )}
+
+
+
+
+ );
+ } else if (tasks && tasks.length > 0) {
+ // Show completion message only if there are tasks and all are done
+ const completedTasks = tasks.filter(task => task.status === 'done').length;
+ const totalTasks = tasks.length;
+
+ bannerContent = (
+
+
+
+
+
+ {completedTasks === totalTasks ? "All done! 🎉" : "No pending tasks"}
+
+
+
+
+ {completedTasks}/{totalTasks}
+
+
+ Review
+
+
+
+
+ );
+ } else {
+ // TaskMaster is configured but no tasks exist - don't show anything in chat
+ bannerContent = null;
+ }
+
+ return (
+ <>
+ {bannerContent}
+
+ {/* Create Task Modal */}
+ {showCreateTaskModal && (
+ setShowCreateTaskModal(false)}
+ onTaskCreated={() => {
+ refreshTasks();
+ setShowCreateTaskModal(false);
+ }}
+ />
+ )}
+
+ {/* Template Selector Modal */}
+ {showTemplateSelector && (
+ setShowTemplateSelector(false)}
+ onTemplateApplied={() => {
+ refreshTasks();
+ setShowTemplateSelector(false);
+ }}
+ />
+ )}
+
+ {/* TaskMaster CLI Setup Modal */}
+ {showCLI && (
+
+
+ {/* Modal Header */}
+
+
+
+
+
+
+ TaskMaster Setup
+ Interactive CLI for {currentProject?.displayName}
+
+
+ setShowCLI(false)}
+ 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-800"
+ >
+
+
+
+
+ {/* Terminal Container */}
+
+
+
+
+
+
+ {/* Modal Footer */}
+
+
+
+ TaskMaster initialization will start automatically
+
+ setShowCLI(false)}
+ className="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-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
+ >
+ Close
+
+
+
+
+
+ )}
+
+ {/* Task Detail Modal */}
+ {showTaskDetail && nextTask && (
+ setShowTaskDetail(false)}
+ onStatusChange={() => refreshTasks?.()}
+ onTaskClick={null} // Disable dependency navigation in NextTaskBanner for now
+ />
+ )}
+ >
+ );
+};
+
+// Simple Create Task Modal Component
+const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
+ const [formData, setFormData] = useState({
+ title: '',
+ description: '',
+ priority: 'medium',
+ useAI: false,
+ prompt: ''
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (!currentProject) return;
+
+ setIsSubmitting(true);
+ try {
+ const taskData = formData.useAI
+ ? { prompt: formData.prompt, priority: formData.priority }
+ : { title: formData.title, description: formData.description, priority: formData.priority };
+
+ const response = await api.taskmaster.addTask(currentProject.name, taskData);
+
+ if (response.ok) {
+ onTaskCreated();
+ } else {
+ const error = await response.json();
+ console.error('Failed to create task:', error);
+ alert(`Failed to create task: ${error.message}`);
+ }
+ } catch (error) {
+ console.error('Error creating task:', error);
+ alert('Error creating task. Please try again.');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+ Create New Task
+
+
+
+
+
+
+
+
+ );
+};
+
+// Template Selector Modal Component
+const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
+ const [templates, setTemplates] = useState([]);
+ const [selectedTemplate, setSelectedTemplate] = useState(null);
+ const [customizations, setCustomizations] = useState({});
+ const [fileName, setFileName] = useState('prd.txt');
+ const [isLoading, setIsLoading] = useState(true);
+ const [isApplying, setIsApplying] = useState(false);
+ const [step, setStep] = useState('select'); // 'select', 'customize', 'generate'
+
+ useEffect(() => {
+ const loadTemplates = async () => {
+ try {
+ const response = await api.taskmaster.getTemplates();
+ if (response.ok) {
+ const data = await response.json();
+ setTemplates(data.templates);
+ }
+ } catch (error) {
+ console.error('Error loading templates:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadTemplates();
+ }, []);
+
+ const handleSelectTemplate = (template) => {
+ setSelectedTemplate(template);
+ // Find placeholders in template content
+ const placeholders = template.content.match(/\[([^\]]+)\]/g) || [];
+ const uniquePlaceholders = [...new Set(placeholders.map(p => p.slice(1, -1)))];
+
+ const initialCustomizations = {};
+ uniquePlaceholders.forEach(placeholder => {
+ initialCustomizations[placeholder] = '';
+ });
+
+ setCustomizations(initialCustomizations);
+ setStep('customize');
+ };
+
+ const handleApplyTemplate = async () => {
+ if (!selectedTemplate || !currentProject) return;
+
+ setIsApplying(true);
+ try {
+ // Apply template
+ const applyResponse = await api.taskmaster.applyTemplate(currentProject.name, {
+ templateId: selectedTemplate.id,
+ fileName,
+ customizations
+ });
+
+ if (!applyResponse.ok) {
+ const error = await applyResponse.json();
+ throw new Error(error.message || 'Failed to apply template');
+ }
+
+ // Parse PRD to generate tasks
+ const parseResponse = await api.taskmaster.parsePRD(currentProject.name, {
+ fileName,
+ numTasks: 10
+ });
+
+ if (!parseResponse.ok) {
+ const error = await parseResponse.json();
+ throw new Error(error.message || 'Failed to generate tasks');
+ }
+
+ setStep('generate');
+ setTimeout(() => {
+ onTemplateApplied();
+ }, 2000);
+
+ } catch (error) {
+ console.error('Error applying template:', error);
+ alert(`Error: ${error.message}`);
+ setIsApplying(false);
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+
+ Loading templates...
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {step === 'select' ? 'Select PRD Template' :
+ step === 'customize' ? 'Customize Template' :
+ 'Generating Tasks'}
+
+
+
+
+
+
+ {step === 'select' && (
+
+ {templates.map((template) => (
+ handleSelectTemplate(template)}
+ >
+
+
+ {template.name}
+ {template.description}
+
+ {template.category}
+
+
+
+
+
+ ))}
+
+ )}
+
+ {step === 'customize' && selectedTemplate && (
+
+
+
+ setFileName(e.target.value)}
+ className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
+ placeholder="prd.txt"
+ />
+
+
+ {Object.keys(customizations).length > 0 && (
+
+
+
+ {Object.entries(customizations).map(([key, value]) => (
+
+
+ setCustomizations(prev => ({ ...prev, [key]: e.target.value }))}
+ className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
+ placeholder={`Enter ${key.toLowerCase()}`}
+ />
+
+ ))}
+
+
+ )}
+
+
+ setStep('select')}
+ className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
+ >
+ Back
+
+
+ {isApplying ? 'Applying...' : 'Apply & Generate Tasks'}
+
+
+
+ )}
+
+ {step === 'generate' && (
+
+
+
+
+
+ Template Applied Successfully!
+
+
+ Your PRD has been created and tasks are being generated...
+
+
+ )}
+
+
+ );
+};
+
+export default NextTaskBanner;
\ No newline at end of file
diff --git a/src/components/PRDEditor.jsx b/src/components/PRDEditor.jsx
new file mode 100644
index 00000000..382f365a
--- /dev/null
+++ b/src/components/PRDEditor.jsx
@@ -0,0 +1,871 @@
+import React, { useState, useEffect, useRef } from 'react';
+import CodeMirror from '@uiw/react-codemirror';
+import { markdown } from '@codemirror/lang-markdown';
+import { oneDark } from '@codemirror/theme-one-dark';
+import { EditorView } from '@codemirror/view';
+import { X, Save, Download, Maximize2, Minimize2, Eye, FileText, Sparkles, AlertTriangle } from 'lucide-react';
+import { cn } from '../lib/utils';
+import { api, authenticatedFetch } from '../utils/api';
+
+const PRDEditor = ({
+ file,
+ onClose,
+ projectPath,
+ project, // Add project object
+ initialContent = '',
+ isNewFile = false,
+ onSave
+}) => {
+ const [content, setContent] = useState(initialContent);
+ const [loading, setLoading] = useState(!isNewFile);
+ const [saving, setSaving] = useState(false);
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const [isDarkMode, setIsDarkMode] = useState(true);
+ const [saveSuccess, setSaveSuccess] = useState(false);
+ const [previewMode, setPreviewMode] = useState(false);
+ const [wordWrap, setWordWrap] = useState(true); // Default to true for markdown
+ const [fileName, setFileName] = useState('');
+ const [showGenerateModal, setShowGenerateModal] = useState(false);
+ const [showOverwriteConfirm, setShowOverwriteConfirm] = useState(false);
+ const [existingPRDs, setExistingPRDs] = useState([]);
+
+ const editorRef = useRef(null);
+
+ const PRD_TEMPLATE = `# Product Requirements Document - Example Project
+
+## 1. Overview
+**Product Name:** AI-Powered Task Manager
+**Version:** 1.0
+**Date:** 2024-12-27
+**Author:** Development Team
+
+This document outlines the requirements for building an AI-powered task management application that integrates with development workflows and provides intelligent task breakdown and prioritization.
+
+## 2. Objectives
+- Create an intuitive task management system that works seamlessly with developer tools
+- Provide AI-powered task generation from high-level requirements
+- Enable real-time collaboration and progress tracking
+- Integrate with popular development environments (VS Code, Cursor, etc.)
+
+### Success Metrics
+- User adoption rate > 80% within development teams
+- Task completion rate improvement of 25%
+- Time-to-delivery reduction of 15%
+
+## 3. User Stories
+
+### Core Functionality
+- As a project manager, I want to create PRDs that automatically generate detailed tasks so I can save time on project planning
+- As a developer, I want to see my next task clearly highlighted so I can maintain focus
+- As a team lead, I want to track progress across multiple projects so I can provide accurate status updates
+- As a developer, I want tasks to be broken down into implementable subtasks so I can work more efficiently
+
+### AI Integration
+- As a user, I want to describe a feature in natural language and get detailed implementation tasks so I can start working immediately
+- As a project manager, I want the AI to analyze task complexity and suggest appropriate time estimates
+- As a developer, I want intelligent task prioritization based on dependencies and deadlines
+
+### Collaboration
+- As a team member, I want to see real-time updates when tasks are completed so I can coordinate my work
+- As a stakeholder, I want to view project progress through intuitive dashboards
+- As a developer, I want to add implementation notes to tasks for future reference
+
+## 4. Functional Requirements
+
+### Task Management
+- Create, edit, and delete tasks with rich metadata (priority, status, dependencies, estimates)
+- Hierarchical task structure with subtasks and sub-subtasks
+- Real-time status updates and progress tracking
+- Dependency management with circular dependency detection
+- Bulk operations (move, update status, assign)
+
+### AI Features
+- Natural language PRD parsing to generate structured tasks
+- Intelligent task breakdown with complexity analysis
+- Automated subtask generation with implementation details
+- Smart dependency suggestion
+- Progress prediction based on historical data
+
+### Integration Features
+- VS Code/Cursor extension for in-editor task management
+- Git integration for linking commits to tasks
+- API for third-party tool integration
+- Webhook support for external notifications
+- CLI tool for command-line task management
+
+### User Interface
+- Responsive web application (desktop and mobile)
+- Multiple view modes (Kanban, list, calendar)
+- Dark/light theme support
+- Drag-and-drop task organization
+- Advanced filtering and search capabilities
+- Keyboard shortcuts for power users
+
+## 5. Technical Requirements
+
+### Frontend
+- React.js with TypeScript for type safety
+- Modern UI framework (Tailwind CSS)
+- State management (Context API or Redux)
+- Real-time updates via WebSockets
+- Progressive Web App (PWA) support
+- Accessibility compliance (WCAG 2.1 AA)
+
+### Backend
+- Node.js with Express.js framework
+- RESTful API design with OpenAPI documentation
+- Real-time communication via Socket.io
+- Background job processing
+- Rate limiting and security middleware
+
+### AI Integration
+- Integration with multiple AI providers (OpenAI, Anthropic, etc.)
+- Fallback model support
+- Context-aware prompt engineering
+- Token usage optimization
+- Model response caching
+
+### Database
+- Primary: PostgreSQL for relational data
+- Cache: Redis for session management and real-time features
+- Full-text search capabilities
+- Database migrations and seeding
+- Backup and recovery procedures
+
+### Infrastructure
+- Docker containerization
+- Cloud deployment (AWS/GCP/Azure)
+- Auto-scaling capabilities
+- Monitoring and logging (structured logging)
+- CI/CD pipeline with automated testing
+
+## 6. Non-Functional Requirements
+
+### Performance
+- Page load time < 2 seconds
+- API response time < 500ms for 95% of requests
+- Support for 1000+ concurrent users
+- Efficient handling of large task lists (10,000+ tasks)
+
+### Security
+- JWT-based authentication with refresh tokens
+- Role-based access control (RBAC)
+- Data encryption at rest and in transit
+- Regular security audits and penetration testing
+- GDPR and privacy compliance
+
+### Reliability
+- 99.9% uptime SLA
+- Graceful error handling and recovery
+- Data backup every 6 hours with point-in-time recovery
+- Disaster recovery plan with RTO < 4 hours
+
+### Scalability
+- Horizontal scaling for both frontend and backend
+- Database read replicas for query optimization
+- CDN for static asset delivery
+- Microservices architecture for future expansion
+
+## 7. User Experience Design
+
+### Information Architecture
+- Intuitive navigation with breadcrumbs
+- Context-aware menus and actions
+- Progressive disclosure of complex features
+- Consistent design patterns throughout
+
+### Interaction Design
+- Smooth animations and transitions
+- Immediate feedback for user actions
+- Undo/redo functionality for critical operations
+- Smart defaults and auto-save features
+
+### Visual Design
+- Modern, clean interface with plenty of whitespace
+- Consistent color scheme and typography
+- Clear visual hierarchy with proper contrast ratios
+- Iconography that supports comprehension
+
+## 8. Integration Requirements
+
+### Development Tools
+- VS Code extension with task panel and quick actions
+- Cursor IDE integration with AI task suggestions
+- Terminal CLI for command-line workflow
+- Browser extension for web-based tools
+
+### Third-Party Services
+- GitHub/GitLab integration for issue sync
+- Slack/Discord notifications
+- Calendar integration (Google Calendar, Outlook)
+- Time tracking tools (Toggl, Harvest)
+
+### APIs and Webhooks
+- RESTful API with comprehensive documentation
+- GraphQL endpoint for complex queries
+- Webhook system for external integrations
+- SDK development for major programming languages
+
+## 9. Implementation Phases
+
+### Phase 1: Core MVP (8-10 weeks)
+- Basic task management (CRUD operations)
+- Simple AI task generation
+- Web interface with essential features
+- User authentication and basic permissions
+
+### Phase 2: Enhanced Features (6-8 weeks)
+- Advanced AI features (complexity analysis, subtask generation)
+- Real-time collaboration
+- Mobile-responsive design
+- Integration with one development tool (VS Code)
+
+### Phase 3: Enterprise Features (4-6 weeks)
+- Advanced user management and permissions
+- API and webhook system
+- Performance optimization
+- Comprehensive testing and security audit
+
+### Phase 4: Ecosystem Expansion (4-6 weeks)
+- Additional tool integrations
+- Mobile app development
+- Advanced analytics and reporting
+- Third-party marketplace preparation
+
+## 10. Risk Assessment
+
+### Technical Risks
+- AI model reliability and cost management
+- Real-time synchronization complexity
+- Database performance with large datasets
+- Integration complexity with multiple tools
+
+### Business Risks
+- User adoption in competitive market
+- AI provider dependency
+- Data privacy and security concerns
+- Feature scope creep and timeline delays
+
+### Mitigation Strategies
+- Implement robust error handling and fallback systems
+- Develop comprehensive testing strategy
+- Create detailed documentation and user guides
+- Establish clear project scope and change management process
+
+## 11. Success Criteria
+
+### Development Milestones
+- Alpha version with core features completed
+- Beta version with selected user group feedback
+- Production-ready version with full feature set
+- Post-launch iterations based on user feedback
+
+### Business Metrics
+- User engagement and retention rates
+- Task completion and productivity metrics
+- Customer satisfaction scores (NPS > 50)
+- Revenue targets and subscription growth
+
+## 12. Appendices
+
+### Glossary
+- **PRD**: Product Requirements Document
+- **AI**: Artificial Intelligence
+- **CRUD**: Create, Read, Update, Delete
+- **API**: Application Programming Interface
+- **CI/CD**: Continuous Integration/Continuous Deployment
+
+### References
+- Industry best practices for task management
+- AI integration patterns and examples
+- Security and compliance requirements
+- Performance benchmarking data
+
+---
+
+**Document Control:**
+- Version: 1.0
+- Last Updated: December 27, 2024
+- Next Review: January 15, 2025
+- Approved By: Product Owner, Technical Lead`;
+
+ // Initialize filename and load content
+ useEffect(() => {
+ const initializeEditor = async () => {
+ // Set initial filename
+ if (file?.name) {
+ setFileName(file.name.replace(/\.(txt|md)$/, '')); // Remove extension for editing
+ } else if (isNewFile) {
+ // Generate default filename based on current date
+ const now = new Date();
+ const dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD
+ setFileName(`prd-${dateStr}`);
+ }
+
+ // Load content
+ if (isNewFile) {
+ setContent(PRD_TEMPLATE);
+ setLoading(false);
+ return;
+ }
+
+ // If content is directly provided (for existing PRDs loaded from API)
+ if (file.content) {
+ setContent(file.content);
+ setLoading(false);
+ return;
+ }
+
+ // Fallback to loading from file path (legacy support)
+ try {
+ setLoading(true);
+
+ const response = await api.readFile(file.projectName, file.path);
+
+ if (!response.ok) {
+ throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ setContent(data.content || PRD_TEMPLATE);
+ } catch (error) {
+ console.error('Error loading PRD file:', error);
+ setContent(`# Error Loading PRD\n\nError: ${error.message}\n\nFile: ${file?.name || 'New PRD'}\nPath: ${file?.path || 'Not saved yet'}\n\n${PRD_TEMPLATE}`);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ initializeEditor();
+ }, [file, projectPath, isNewFile]);
+
+ // Fetch existing PRDs to check for conflicts
+ useEffect(() => {
+ const fetchExistingPRDs = async () => {
+ if (!project?.name) {
+ console.log('No project name available:', project);
+ return;
+ }
+
+ try {
+ console.log('Fetching PRDs for project:', project.name);
+ const response = await api.get(`/taskmaster/prd/${encodeURIComponent(project.name)}`);
+ if (response.ok) {
+ const data = await response.json();
+ console.log('Fetched existing PRDs:', data.prds);
+ setExistingPRDs(data.prds || []);
+ } else {
+ console.log('Failed to fetch PRDs:', response.status, response.statusText);
+ }
+ } catch (error) {
+ console.error('Error fetching existing PRDs:', error);
+ }
+ };
+
+ fetchExistingPRDs();
+ }, [project?.name]);
+
+ const handleSave = async () => {
+ if (!content.trim()) {
+ alert('Please add content before saving.');
+ return;
+ }
+
+ if (!fileName.trim()) {
+ alert('Please provide a filename for the PRD.');
+ return;
+ }
+
+ // Check if file already exists
+ const fullFileName = fileName.endsWith('.txt') || fileName.endsWith('.md') ? fileName : `${fileName}.txt`;
+ const existingFile = existingPRDs.find(prd => prd.name === fullFileName);
+
+ console.log('Save check:', {
+ fullFileName,
+ existingPRDs,
+ existingFile,
+ isExisting: file?.isExisting,
+ fileObject: file,
+ shouldShowModal: existingFile && !file?.isExisting
+ });
+
+ if (existingFile && !file?.isExisting) {
+ console.log('Showing overwrite confirmation modal');
+ // Show confirmation modal for overwrite
+ setShowOverwriteConfirm(true);
+ return;
+ }
+
+ await performSave();
+ };
+
+ const performSave = async () => {
+ setSaving(true);
+ try {
+ // Ensure filename has .txt extension
+ const fullFileName = fileName.endsWith('.txt') || fileName.endsWith('.md') ? fileName : `${fileName}.txt`;
+
+ const response = await authenticatedFetch(`/api/taskmaster/prd/${encodeURIComponent(project?.name)}`, {
+ method: 'POST',
+ body: JSON.stringify({
+ fileName: fullFileName,
+ content
+ })
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.message || `Save failed: ${response.status}`);
+ }
+
+ // Show success feedback
+ setSaveSuccess(true);
+ setTimeout(() => setSaveSuccess(false), 2000);
+
+ // Update existing PRDs list
+ const response2 = await api.get(`/taskmaster/prd/${encodeURIComponent(project.name)}`);
+ if (response2.ok) {
+ const data = await response2.json();
+ setExistingPRDs(data.prds || []);
+ }
+
+ // Call the onSave callback if provided (for UI updates)
+ if (onSave) {
+ await onSave();
+ }
+
+ } catch (error) {
+ console.error('Error saving PRD:', error);
+ alert(`Error saving PRD: ${error.message}`);
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleDownload = () => {
+ const blob = new Blob([content], { type: 'text/markdown' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ const downloadFileName = fileName ? `${fileName}.txt` : 'prd.txt';
+ a.download = downloadFileName;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
+
+ const handleGenerateTasks = async () => {
+ if (!content.trim()) {
+ alert('Please add content to the PRD before generating tasks.');
+ return;
+ }
+
+ // Show AI-first modal instead of simple confirm
+ setShowGenerateModal(true);
+ };
+
+
+ const toggleFullscreen = () => {
+ setIsFullscreen(!isFullscreen);
+ };
+
+ // Handle keyboard shortcuts
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ if (e.ctrlKey || e.metaKey) {
+ if (e.key === 's') {
+ e.preventDefault();
+ handleSave();
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ onClose();
+ }
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyDown);
+ return () => document.removeEventListener('keydown', handleKeyDown);
+ }, [content]);
+
+ // Simple markdown to HTML converter for preview
+ const renderMarkdown = (markdown) => {
+ return markdown
+ .replace(/^### (.*$)/gim, '$1
')
+ .replace(/^## (.*$)/gim, '$1
')
+ .replace(/^# (.*$)/gim, '$1
')
+ .replace(/\*\*(.*)\*\*/gim, '$1')
+ .replace(/\*(.*)\*/gim, '$1')
+ .replace(/^\- (.*$)/gim, '$1 ')
+ .replace(/(.*<\/li>)/gims, '$1
')
+ .replace(/\n\n/gim, '')
+ .replace(/^(?!<[h|u|l])(.*$)/gim, '
$1
')
+ .replace(/<\/ul>\s*/gim, '');
+ };
+
+ if (loading) {
+ return (
+
+
+
+
+ Loading PRD...
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+
+ {/* Mobile: Stack filename and tags vertically for more space */}
+
+ {/* Filename input row - full width on mobile */}
+
+
+ {
+ // Remove invalid filename characters
+ const sanitizedValue = e.target.value.replace(/[<>:"/\\|?*]/g, '');
+ setFileName(sanitizedValue);
+ }}
+ 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"
+ placeholder="Enter PRD filename"
+ maxLength={100}
+ />
+ .txt
+
+ document.querySelector('input[placeholder="Enter PRD filename"]')?.focus()}
+ className="p-1 text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
+ title="Click to edit filename"
+ >
+
+
+
+
+
+
+ {/* Tags row - moves to second line on mobile for more filename space */}
+
+
+ 📋 PRD
+
+ {isNewFile && (
+
+ ✨ New
+
+ )}
+
+
+
+ {/* Description - smaller on mobile */}
+
+ Product Requirements Document
+
+
+
+
+
+ setPreviewMode(!previewMode)}
+ className={cn(
+ 'p-2 md:p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800',
+ 'min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center',
+ previewMode
+ ? 'text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-900'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
+ )}
+ title={previewMode ? 'Switch to edit mode' : 'Preview markdown'}
+ >
+
+
+
+ setWordWrap(!wordWrap)}
+ className={cn(
+ 'p-2 md:p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800',
+ 'min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center',
+ wordWrap
+ ? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900'
+ : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
+ )}
+ title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
+ >
+ ↵
+
+
+ setIsDarkMode(!isDarkMode)}
+ className="p-2 md: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 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
+ title="Toggle theme"
+ >
+ {isDarkMode ? '☀️' : '🌙'}
+
+
+
+
+
+
+
+
+ Generate Tasks
+
+
+
+ {saveSuccess ? (
+ <>
+
+
+
+ Saved!
+ >
+ ) : (
+ <>
+
+ {saving ? 'Saving...' : 'Save PRD'}
+ >
+ )}
+
+
+
+ {isFullscreen ? : }
+
+
+
+
+
+
+
+
+ {/* Editor/Preview Content */}
+
+ {previewMode ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ {/* Footer */}
+
+
+ Lines: {content.split('\n').length}
+ Characters: {content.length}
+ Words: {content.split(/\s+/).filter(word => word.length > 0).length}
+ Format: Markdown
+
+
+
+ Press Ctrl+S to save • Esc to close
+
+
+
+
+ {/* Generate Tasks Modal */}
+ {showGenerateModal && (
+
+
+ {/* Header */}
+
+
+
+
+
+ Generate Tasks from PRD
+
+ setShowGenerateModal(false)}
+ 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"
+ >
+
+
+
+
+ {/* Content */}
+
+ {/* AI-First Approach */}
+
+
+
+
+
+
+
+ 💡 Pro Tip: Ask Claude Code Directly!
+
+
+ You can simply ask Claude Code in the chat to parse your PRD and generate tasks.
+ The AI assistant will automatically save your PRD and create detailed tasks with implementation details.
+
+
+
+ 💬 Example:
+
+ "I've just initialized a new project with Claude Task Master. I have a PRD at .taskmaster/docs/{fileName.endsWith('.txt') || fileName.endsWith('.md') ? fileName : `${fileName}.txt`}. Can you help me parse it and set up the initial tasks?"
+
+
+
+
+ This will: Save your PRD, analyze its content, and generate structured tasks with subtasks, dependencies, and implementation details.
+
+
+
+
+
+ {/* Learn More Link */}
+
+
+ For more examples and advanced usage patterns:
+
+
+ View TaskMaster Documentation →
+
+
+
+ {/* Footer */}
+
+ setShowGenerateModal(false)}
+ 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"
+ >
+ Got it, I'll ask Claude Code directly
+
+
+
+
+
+ )}
+
+ {/* Overwrite Confirmation Modal */}
+ {showOverwriteConfirm && (
+
+ setShowOverwriteConfirm(false)} />
+
+
+
+
+
+
+
+ File Already Exists
+
+
+
+
+ A PRD file named "{fileName.endsWith('.txt') || fileName.endsWith('.md') ? fileName : `${fileName}.txt`}" already exists.
+ Do you want to overwrite it with the current content?
+
+
+
+ setShowOverwriteConfirm(false)}
+ 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"
+ >
+ Cancel
+
+ {
+ setShowOverwriteConfirm(false);
+ await performSave();
+ }}
+ className="px-4 py-2 text-sm text-white bg-yellow-600 hover:bg-yellow-700 rounded-md flex items-center space-x-2 transition-colors"
+ >
+
+ Overwrite
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default PRDEditor;
\ No newline at end of file
diff --git a/src/components/SetupForm.jsx b/src/components/SetupForm.jsx
index 08b74b99..f1aa497e 100644
--- a/src/components/SetupForm.jsx
+++ b/src/components/SetupForm.jsx
@@ -1,10 +1,8 @@
import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import ClaudeLogo from './ClaudeLogo';
-import { translations } from '../lib/i18n.js';
const SetupForm = () => {
- const t = (key) => translations[key] || key;
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -18,17 +16,17 @@ const SetupForm = () => {
setError('');
if (password !== confirmPassword) {
- setError(t('Passwords do not match'));
+ setError('Passwords do not match');
return;
}
if (username.length < 3) {
- setError(t('Username must be at least 3 characters long'));
+ setError('Username must be at least 3 characters long');
return;
}
if (password.length < 6) {
- setError(t('Password must be at least 6 characters long'));
+ setError('Password must be at least 6 characters long');
return;
}
@@ -52,9 +50,9 @@ const SetupForm = () => {
- {t("Welcome to Claude Code UI")}
+ Welcome to Claude Code UI
- {t("Set up your account to get started")}
+ Set up your account to get started
@@ -62,7 +60,7 @@ const SetupForm = () => {
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 10f36762..aff26d76 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
-import { translations } from '../lib/i18n.js';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
@@ -9,10 +8,14 @@ import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRig
import { cn } from '../lib/utils';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo.jsx';
+import TaskIndicator from './TaskIndicator';
import { api } from '../utils/api';
+import { useTaskMaster } from '../contexts/TaskMasterContext';
+import { useTasksSettings } from '../contexts/TasksSettingsContext';
+import { t } from '../lib/i18n';
// Move formatTimeAgo outside component to avoid recreation on every render
-const formatTimeAgo = (dateString, currentTime, t) => {
+const formatTimeAgo = (dateString, currentTime) => {
const date = new Date(dateString);
const now = currentTime;
@@ -27,13 +30,13 @@ const formatTimeAgo = (dateString, currentTime, t) => {
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60));
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
- if (diffInSeconds < 60) return t('Just now');
- if (diffInMinutes === 1) return t('1 min ago');
- if (diffInMinutes < 60) return `${diffInMinutes}${t(' mins ago')}`;
- if (diffInHours === 1) return t('1 hour ago');
- if (diffInHours < 24) return `${diffInHours}${t(' hours ago')}`;
- if (diffInDays === 1) return t('1 day ago');
- if (diffInDays < 7) return `${diffInDays}${t(' days ago')}`;
+ if (diffInSeconds < 60) return t('justNow');
+ if (diffInMinutes === 1) return t('oneMinAgo');
+ if (diffInMinutes < 60) return t('minsAgo', { count: diffInMinutes });
+ if (diffInHours === 1) return t('oneHourAgo');
+ if (diffInHours < 24) return t('hoursAgo', { count: diffInHours });
+ if (diffInDays === 1) return t('oneDayAgo');
+ if (diffInDays < 7) return t('daysAgo', { count: diffInDays });
return date.toLocaleDateString();
};
@@ -54,7 +57,6 @@ function Sidebar({
currentVersion,
onShowVersionModal
}) {
- const t = (key) => translations[key] || key;
const [expandedProjects, setExpandedProjects] = useState(new Set());
const [editingProject, setEditingProject] = useState(null);
const [showNewProject, setShowNewProject] = useState(false);
@@ -72,6 +74,10 @@ function Sidebar({
const [generatingSummary, setGeneratingSummary] = useState({});
const [searchFilter, setSearchFilter] = useState('');
+ // TaskMaster context
+ const { setCurrentProject, mcpServerStatus } = useTaskMaster();
+ const { tasksEnabled } = useTasksSettings();
+
// Starred projects state - persisted in localStorage
const [starredProjects, setStarredProjects] = useState(() => {
@@ -79,7 +85,7 @@ function Sidebar({
const saved = localStorage.getItem('starredProjects');
return saved ? new Set(JSON.parse(saved)) : new Set();
} catch (error) {
- console.error(t('Error loading starred projects:'), error);
+ console.error(t('errorLoadingStarred'), error);
return new Set();
}
});
@@ -142,7 +148,7 @@ function Sidebar({
setProjectSortOrder(settings.projectSortOrder || 'name');
}
} catch (error) {
- console.error(t('Error loading sort order:'), error);
+ console.error(t('errorLoadingSortOrder'), error);
}
};
@@ -195,7 +201,7 @@ function Sidebar({
try {
localStorage.setItem('starredProjects', JSON.stringify([...newStarred]));
} catch (error) {
- console.error(t('Error saving starred projects:'), error);
+ console.error(t('errorSavingStarred'), error);
}
};
@@ -272,10 +278,10 @@ function Sidebar({
window.location.reload();
}
} else {
- console.error(t('Failed to rename project'));
+ console.error(t('failedToRenameProject'));
}
} catch (error) {
- console.error(t('Error renaming project:'), error);
+ console.error(t('errorRenamingProject'), error);
}
setEditingProject(null);
@@ -283,7 +289,7 @@ function Sidebar({
};
const deleteSession = async (projectName, sessionId) => {
- if (!confirm(t('Are you sure you want to delete this session? This action cannot be undone.'))) {
+ if (!confirm(t('confirmSessionDelete'))) {
return;
}
@@ -296,17 +302,17 @@ function Sidebar({
onSessionDelete(sessionId);
}
} else {
- console.error(t('Failed to delete session'));
- alert(t('Failed to delete session. Please try again.'));
+ console.error(t('failedToDeleteSession'));
+ alert(t('failedToDeleteSession'));
}
} catch (error) {
- console.error(t('Error deleting session:'), error);
- alert(t('Error deleting session. Please try again.'));
+ console.error(t('errorDeletingSession'), error);
+ alert(t('errorDeletingSession'));
}
};
const deleteProject = async (projectName) => {
- if (!confirm(t('Are you sure you want to delete this empty project? This action cannot be undone.'))) {
+ if (!confirm(t('confirmProjectDelete'))) {
return;
}
@@ -320,18 +326,18 @@ function Sidebar({
}
} else {
const error = await response.json();
- console.error(t('Failed to delete project'));
- alert(error.error || t('Failed to delete project. Please try again.'));
+ console.error(t('failedToDeleteProject'));
+ alert(error.error || t('failedToDeleteProject'));
}
} catch (error) {
- console.error(t('Error deleting project:'), error);
- alert(t('Error deleting project. Please try again.'));
+ console.error(t('errorDeletingProject'), error);
+ alert(t('errorDeletingProject'));
}
};
const createNewProject = async () => {
if (!newProjectPath.trim()) {
- alert(t('Please enter a project path'));
+ alert(t('enterProjectPath'));
return;
}
@@ -353,11 +359,11 @@ function Sidebar({
}
} else {
const error = await response.json();
- alert(error.error || t('Failed to create project. Please try again.'));
+ alert(error.error || t('failedToCreateProject'));
}
} catch (error) {
- console.error(t('Error creating project:'), error);
- alert(t('Error creating project. Please try again.'));
+ console.error(t('errorCreatingProject'), error);
+ alert(t('errorCreatingProject'));
} finally {
setCreatingProject(false);
}
@@ -401,7 +407,7 @@ function Sidebar({
}
}
} catch (error) {
- console.error(t('Error loading more sessions:'), error);
+ console.error(t('errorLoadingMoreSessions'), error);
} finally {
setLoadingSessions(prev => ({ ...prev, [project.name]: false }));
}
@@ -419,6 +425,15 @@ function Sidebar({
return displayName.includes(searchLower) || projectName.includes(searchLower);
});
+ // Enhanced project selection that updates both the main UI and TaskMaster context
+ const handleProjectSelect = (project) => {
+ // Call the original project select handler
+ onProjectSelect(project);
+
+ // Update TaskMaster context with the selected project
+ setCurrentProject(project);
+ };
+
return (
{/* Header */}
@@ -430,8 +445,8 @@ function Sidebar({
- {t("Claude Code UI")}
- {t("AI coding assistant interface")}
+ {t('claudeCodeUI')}
+ {t('aiCodingAssistant')}
@@ -448,7 +463,7 @@ function Sidebar({
}
}}
disabled={isRefreshing}
- title={t("Refresh projects and sessions (Ctrl+R)")}
+ title={t('refreshProjects')}
>
@@ -457,7 +472,7 @@ function Sidebar({
size="sm"
className="h-9 w-9 px-0 bg-primary hover:bg-primary/90 transition-all duration-200 shadow-sm hover:shadow-md"
onClick={() => setShowNewProject(true)}
- title={t("Create new project (Ctrl+N)")}
+ title={t('createNewProject')}
>
@@ -472,8 +487,8 @@ function Sidebar({
- {t("Claude Code UI")}
- {t("Projects")}
+ {t('claudeCodeUI')}
+ {t('Projects')}
@@ -502,6 +517,7 @@ function Sidebar({
+
{/* New Project Form */}
{showNewProject && (
@@ -509,12 +525,12 @@ function Sidebar({
- {t("Create New Project")}
+ {t('Create New Project')}
setNewProjectPath(e.target.value)}
- placeholder={t("/path/to/project or relative/path")}
+ placeholder={t('projectPathPlaceholder')}
className="text-sm focus:ring-2 focus:ring-primary/20"
autoFocus
onKeyDown={(e) => {
@@ -538,7 +554,7 @@ function Sidebar({
disabled={creatingProject}
className="h-8 text-xs hover:bg-accent transition-colors"
>
- {t("Cancel")}
+ {t('Cancel')}
@@ -552,7 +568,7 @@ function Sidebar({
- {t("New Project")}
+ {t('New Project')}
setNewProjectPath(e.target.value)}
- placeholder={t("/path/to/project or relative/path")}
+ placeholder={t('projectPathPlaceholder')}
className="text-sm h-10 rounded-md focus:border-primary transition-colors"
autoFocus
onKeyDown={(e) => {
@@ -584,7 +600,7 @@ function Sidebar({
variant="outline"
className="flex-1 h-9 text-sm rounded-md active:scale-95 transition-transform"
>
- {t("Cancel")}
+ {t('Cancel')}
setSearchFilter(e.target.value)}
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
@@ -635,9 +651,9 @@ function Sidebar({
- {t("Loading projects...")}
+ {t('loadingProjects')}
- {t("Fetching your Claude projects and sessions")}
+ {t('fetchingProjects')}
- {t("Run Claude CLI in a project directory to get started")} + {t('runClaudeCLI')}
) : filteredProjects.length === 0 ? ( @@ -655,9 +671,9 @@ function Sidebar({- {t("Try adjusting your search term")} + {t('adjustSearch')}
) : ( @@ -703,7 +719,7 @@ function Sidebar({ value={editingName} onChange={(e) => setEditingName(e.target.value)} className="w-full px-3 py-2 text-sm border-2 border-primary/40 focus:border-primary rounded-lg bg-background text-foreground shadow-sm focus:shadow-md transition-all duration-200 focus:outline-none" - placeholder={t("Project name")} + placeholder={t('projectName')} autoFocus autoComplete="off" onClick={(e) => e.stopPropagation()} @@ -719,15 +735,31 @@ function Sidebar({ /> ) : ( <> -{(() => { const sessionCount = getAllSessions(project).length; const hasMore = project.sessionMeta?.hasMore !== false; const count = hasMore && sessionCount >= 5 ? `${sessionCount}+` : sessionCount; - return `${count} ${count === 1 ? t('session') : t('sessions')}`; + return t('sessionCount', { count }); })()}
> @@ -771,7 +803,7 @@ function Sidebar({ toggleStarProject(project.name); }} onTouchEnd={handleTouchClick(() => toggleStarProject(project.name))} - title={isStarred ? t("Remove from favorites") : t("Add to favorites")} + title={isStarred ? t('removeFromFavorites') : t('addToFavorites')} >{t("No sessions yet")}
+{t('noSessionsYet')}
+ {task.testStrategy} +
++ {subtask.description} +
+ )} ++ TaskMaster helps break down complex projects into manageable tasks with AI-powered assistance +
+ + {/* What is TaskMaster section */} +• AI-Powered Task Management: Break complex projects into manageable subtasks
+• PRD Templates: Generate tasks from Product Requirements Documents
+• Dependency Tracking: Understand task relationships and execution order
+• Progress Visualization: Kanban boards and detailed task analytics
+• CLI Integration: Use taskmaster commands for advanced workflows
+TaskMaster is initialized! Here's what to do next:
+Discuss your project idea and create a PRD that describes what you want to build.
+Existing PRDs:
+Once you have a PRD, ask your AI assistant to parse it and TaskMaster will automatically break it down into manageable tasks with implementation details.
+Ask your AI assistant to analyze task complexity and expand them into detailed subtasks for easier implementation.
+Ask your AI assistant to begin working on tasks, update their status, and add new tasks as your project evolves.
+Interactive CLI for {currentProject?.displayName}
+Try adjusting your search or filter criteria.
+Your guide to productive task management
+Discuss your project idea and create a PRD that describes what you want to build.
+Once you have a PRD, ask your AI assistant to parse it and TaskMaster will automatically break it down into manageable tasks with implementation details.
+💬 Example:
++ "I've just initialized a new project with Claude Task Master. I have a PRD at .taskmaster/docs/prd.txt. Can you help me parse it and set up the initial tasks?" +
+Ask your AI assistant to analyze task complexity and expand them into detailed subtasks for easier implementation.
+💬 Example:
++ "Task 5 seems complex. Can you break it down into subtasks?" +
+Ask your AI assistant to begin working on tasks, update their status, and add new tasks as your project evolves.
+💬 Example:
++ "Please add a new task to implement user profile image uploads using Cloudinary, research the best approach." +
++ TaskMaster AI is an advanced task management system built for developers. Get documentation, examples, and contribute to the project. +
+ ++ Configure TaskMaster settings for your project +
++ TaskMaster works best with the MCP server configured +
++ To enable full TaskMaster integration, add the MCP server configuration to your Claude settings. +
+ +
+{`{
+ "mcpServers": {
+ "task-master-ai": {
+ "command": "npx",
+ "args": ["-y", "--package=task-master-ai", "task-master-ai"],
+ "env": {
+ "ANTHROPIC_API_KEY": "your_anthropic_key_here",
+ "PERPLEXITY_API_KEY": "your_perplexity_key_here"
+ }
+ }
+ }
+}`}
+
+ + Create or import a PRD to generate initial tasks +
++ TaskMaster will analyze your PRD and automatically generate a structured task list with dependencies, priorities, and implementation details. +
++ Ready to initialize TaskMaster for your project +
+.taskmaster/docs/prd.txt+ Step {currentStep} of {totalSteps}: {steps[currentStep - 1]?.description} +
+{error}
+- {t("Tools that are automatically allowed without prompting for permission")} + Tools that are automatically allowed without prompting for permission
- {t("Quick add common tools:")} + Quick add common tools:
- {t("Tools that are automatically blocked without prompting for permission")} + Tools that are automatically blocked without prompting for permission
"Bash(git log:*)" - {t("Allow all git log commands")}"Bash(git diff:*)" - {t("Allow all git diff commands")}"Write" - {t("Allow all Write tool usage")}"Read" - {t("Allow all Read tool usage")}"Bash(rm:*)" - {t("Block all rm commands (dangerous)")}"Bash(git log:*)" - Allow all git log commands"Bash(git diff:*)" - Allow all git diff commands"Write" - Allow all Write tool usage"Read" - Allow all Read tool usage"Bash(rm:*)" - Block all rm commands (dangerous)- {t("Model Context Protocol servers provide additional tools and data sources to Claude")} + Model Context Protocol servers provide additional tools and data sources to Claude
{server.config.command}{server.config.command}{server.config.url}{server.config.url}{server.config.args.join(' ')}{server.config.args.join(' ')}{Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}{Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}
{JSON.stringify(server.raw, null, 2)}
@@ -1015,11 +1033,11 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) {
{/* Tools Discovery Results */}
{mcpServerTools[server.id] && (
- {t("Scope cannot be changed when editing an existing server")} + Scope cannot be changed when editing an existing server
{mcpFormData.scope === 'user' - ? t('User scope: Available across all projects on your machine') - : t('Local scope: Only available in the selected project') + ? 'User scope: Available across all projects on your machine' + : 'Local scope: Only available in the selected project' }
- {t("Path:")} {mcpFormData.projectPath} + Path: {mcpFormData.projectPath}
)}- Check if the project path is accessible + {t('checkIfPathAccessible')}
- Sign in to your Claude Code UI account + {t('signInToYourClaudeCodeUIAccount')}
@@ -51,7 +52,7 @@ const LoginForm = () => {