/* * MainContent.jsx - Main Content Area with Session Protection Props Passthrough * * SESSION PROTECTION PASSTHROUGH: * =============================== * * This component serves as a passthrough layer for Session Protection functions: * - Receives session management functions from App.jsx * - Passes them down to ChatInterface.jsx * * No session protection logic is implemented here - it's purely a props bridge. */ import React, { useState, useEffect } from 'react'; import ChatInterface from './ChatInterface'; import FileTree from './FileTree'; import CodeEditor from './CodeEditor'; import StandaloneShell from './StandaloneShell'; import GitPanel from './GitPanel'; import ErrorBoundary from './ErrorBoundary'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo'; import TaskList from './TaskList'; import TaskDetail from './TaskDetail'; import PRDEditor from './PRDEditor'; import Tooltip from './Tooltip'; import { useTaskMaster } from '../contexts/TaskMasterContext'; import { useTasksSettings } from '../contexts/TasksSettingsContext'; import { api } from '../utils/api'; function MainContent({ selectedProject, selectedSession, activeTab, setActiveTab, ws, sendMessage, messages, isMobile, isPWA, onMenuClick, isLoading, onInputFocusChange, // Session Protection Props: Functions passed down from App.jsx to manage active session state // These functions control when project updates are paused during active conversations onSessionActive, // Mark session as active when user sends message onSessionInactive, // Mark session as inactive when conversation completes/aborts onReplaceTemporarySession, // Replace temporary session ID with real session ID from WebSocket onNavigateToSession, // Navigate to a specific session (for Claude CLI session duplication workaround) onShowSettings, // Show tools settings panel autoExpandTools, // Auto-expand tool accordions showRawParameters, // Show raw parameters in tool accordions autoScrollToBottom, // Auto-scroll to bottom when new messages arrive sendByCtrlEnter // Send by Ctrl+Enter mode for East Asian language input }) { const [editingFile, setEditingFile] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const [showTaskDetail, setShowTaskDetail] = useState(false); // PRD Editor state const [showPRDEditor, setShowPRDEditor] = useState(false); const [selectedPRD, setSelectedPRD] = useState(null); const [existingPRDs, setExistingPRDs] = useState([]); const [prdNotification, setPRDNotification] = useState(null); // TaskMaster context const { tasks, currentProject, refreshTasks, setCurrentProject } = useTaskMaster(); const { tasksEnabled, isTaskMasterInstalled, isTaskMasterReady } = useTasksSettings(); // Only show tasks tab if TaskMaster is installed and enabled const shouldShowTasksTab = tasksEnabled && isTaskMasterInstalled; // Sync selectedProject with TaskMaster context useEffect(() => { if (selectedProject && selectedProject !== currentProject) { setCurrentProject(selectedProject); } }, [selectedProject, currentProject, setCurrentProject]); // Switch away from tasks tab when tasks are disabled or TaskMaster is not installed useEffect(() => { if (!shouldShowTasksTab && activeTab === 'tasks') { setActiveTab('chat'); } }, [shouldShowTasksTab, activeTab, setActiveTab]); // Load existing PRDs when current project changes useEffect(() => { const loadExistingPRDs = async () => { if (!currentProject?.name) { setExistingPRDs([]); return; } try { const response = await api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}`); if (response.ok) { const data = await response.json(); setExistingPRDs(data.prdFiles || []); } else { setExistingPRDs([]); } } catch (error) { console.error('Failed to load existing PRDs:', error); setExistingPRDs([]); } }; loadExistingPRDs(); }, [currentProject?.name]); const handleFileOpen = (filePath, diffInfo = null) => { // Create a file object that CodeEditor expects const file = { name: filePath.split('/').pop(), path: filePath, projectName: selectedProject?.name, diffInfo: diffInfo // Pass along diff information if available }; setEditingFile(file); }; const handleCloseEditor = () => { setEditingFile(null); }; const handleTaskClick = (task) => { // If task is just an ID (from dependency click), find the full task object if (typeof task === 'object' && task.id && !task.title) { const fullTask = tasks?.find(t => t.id === task.id); if (fullTask) { setSelectedTask(fullTask); setShowTaskDetail(true); } } else { setSelectedTask(task); setShowTaskDetail(true); } }; const handleTaskDetailClose = () => { setShowTaskDetail(false); setSelectedTask(null); }; const handleTaskStatusChange = (taskId, newStatus) => { // This would integrate with TaskMaster API to update task status console.log('Update task status:', taskId, newStatus); refreshTasks?.(); }; if (isLoading) { return (
{/* Header with menu button for mobile */} {isMobile && (
)}

Loading Claude Code UI

Setting up your workspace...

); } if (!selectedProject) { return (
{/* Header with menu button for mobile */} {isMobile && (
)}

Choose Your Project

Select a project from the sidebar to start coding with Claude. Each project contains your chat sessions and file history.

💡 Tip: {isMobile ? 'Tap the menu button above to access projects' : 'Create a new project by clicking the folder icon in the sidebar'}

); } return (
{/* Header with tabs */}
{isMobile && ( )}
{activeTab === 'chat' && selectedSession && (
{selectedSession.__provider === 'cursor' ? ( ) : ( )}
)}
{activeTab === 'chat' && selectedSession ? (

{selectedSession.__provider === 'cursor' ? (selectedSession.name || 'Untitled Session') : (selectedSession.summary || 'New Session')}

{selectedProject.displayName} • {selectedSession.id}
) : activeTab === 'chat' && !selectedSession ? (

New Session

{selectedProject.displayName}
) : (

{activeTab === 'files' ? 'Project Files' : activeTab === 'git' ? 'Source Control' : (activeTab === 'tasks' && shouldShowTasksTab) ? 'TaskMaster' : 'Project'}

{selectedProject.displayName}
)}
{/* Modern Tab Navigation - Right Side */}
{shouldShowTasksTab && ( )} {/* */}
{/* Content Area */}
setActiveTab('tasks') : null} />
{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)); } }} />
)}
{/* { sendMessage({ type: 'server:start', projectPath: selectedProject?.fullPath, script: script }); }} onStopServer={() => { sendMessage({ type: 'server:stop', projectPath: selectedProject?.fullPath }); }} onScriptSelect={setCurrentScript} currentScript={currentScript} isMobile={isMobile} serverLogs={serverLogs} onClearLogs={() => setServerLogs([])} /> */}
{/* Code Editor Modal */} {editingFile && ( )} {/* 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}
)}
); } export default React.memo(MainContent);