/*
* 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 && (
)}
);
}
export default React.memo(MainContent);