feat: Improve dashboard layout and Claude terminal context

- Reorganize dashboard: control panel on top, charts stacked vertically
- Add Set Context button to Claude terminal for study awareness
- Add conda environment instructions to CLAUDE.md
- Fix STUDY_REPORT.md location in generate-report.md skill
- Claude terminal now sends study context with skills reminder

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Antoine
2025-12-04 20:59:31 -05:00
parent f8b90156b3
commit fb2d06236a
5 changed files with 309 additions and 211 deletions

View File

@@ -394,7 +394,7 @@ Would you like me to:
When a STUDY_REPORT.md file is generated, it can be viewed directly in the Atomizer Dashboard:
1. **Save report to**: `studies/{study_name}/2_results/STUDY_REPORT.md`
1. **Save report to**: `studies/{study_name}/STUDY_REPORT.md` (study root folder)
2. **View in dashboard**: Click "Study Report" button on the dashboard
3. **Features**:
- Full markdown rendering with proper typography

View File

@@ -51,6 +51,26 @@ When optimization needs >50 trials:
- Suggest fixes
- Recover from failures
## Python Environment
**CRITICAL: Always use the `atomizer` conda environment.** All dependencies are pre-installed.
```bash
# Activate before ANY Python command
conda activate atomizer
# Then run scripts
python run_optimization.py --start
python -m optimization_engine.runner ...
```
**DO NOT:**
- Install packages with pip/conda (everything is already installed)
- Create new virtual environments
- Use system Python
**Pre-installed packages include:** optuna, numpy, scipy, pandas, matplotlib, pyNastran, torch, plotly, and all Atomizer dependencies.
## Key Files & Locations
```

View File

@@ -9,7 +9,8 @@ import {
Minimize2,
X,
RefreshCw,
AlertCircle
AlertCircle,
FolderOpen
} from 'lucide-react';
import { useStudy } from '../context/StudyContext';
@@ -33,6 +34,8 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
const [isConnecting, setIsConnecting] = useState(false);
const [_error, setError] = useState<string | null>(null);
const [cliAvailable, setCliAvailable] = useState<boolean | null>(null);
const [contextSet, setContextSet] = useState(false);
const [settingContext, setSettingContext] = useState(false);
// Check CLI availability
useEffect(() => {
@@ -165,8 +168,7 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
xtermRef.current?.clear();
xtermRef.current?.writeln('\x1b[1;32mConnected to Claude Code\x1b[0m');
if (selectedStudy?.id) {
xtermRef.current?.writeln(`\x1b[90mStudy context: \x1b[1;33m${selectedStudy.id}\x1b[0m`);
xtermRef.current?.writeln('\x1b[90mTip: Tell Claude about your study, e.g. "Help me with study ' + selectedStudy.id + '"\x1b[0m');
xtermRef.current?.writeln(`\x1b[90mStudy: \x1b[1;33m${selectedStudy.id}\x1b[0m \x1b[90m- Click "Set Context" to initialize\x1b[0m`);
}
xtermRef.current?.writeln('');
@@ -241,8 +243,26 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
wsRef.current = null;
}
setIsConnected(false);
setContextSet(false);
}, []);
// Set study context - sends context message to Claude silently
const setStudyContext = useCallback(() => {
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN || !selectedStudy?.id) return;
setSettingContext(true);
// Send context message - Claude should use CLAUDE.md and .claude/skills/ for guidance
const contextMessage = `Context: Working on study "${selectedStudy.id}" at studies/${selectedStudy.id}/. ` +
`Read .claude/skills/ for task protocols. Use atomizer conda env. Acknowledge briefly.`;
wsRef.current.send(JSON.stringify({ type: 'input', data: contextMessage + '\n' }));
// Mark as done after Claude has had time to process
setTimeout(() => {
setSettingContext(false);
setContextSet(true);
}, 500);
}, [selectedStudy]);
// Cleanup on unmount
useEffect(() => {
return () => {
@@ -293,6 +313,35 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
{isConnected ? 'Disconnect' : 'Connect'}
</button>
{/* Set Context button - always show, with different states */}
<button
onClick={setStudyContext}
disabled={!selectedStudy?.id || !isConnected || settingContext || contextSet}
className={`px-3 py-1.5 text-sm rounded-lg transition-colors flex items-center gap-2 ${
contextSet
? 'bg-green-600/20 text-green-400'
: !selectedStudy?.id || !isConnected
? 'bg-dark-600 text-dark-400'
: 'bg-primary-600/20 text-primary-400 hover:bg-primary-600/30'
} disabled:opacity-50 disabled:cursor-not-allowed`}
title={
!selectedStudy?.id
? 'No study selected - select a study from Home page'
: !isConnected
? 'Connect first to set study context'
: contextSet
? 'Context already set'
: `Set context to study: ${selectedStudy.id}`
}
>
{settingContext ? (
<RefreshCw className="w-3 h-3 animate-spin" />
) : (
<FolderOpen className="w-3 h-3" />
)}
{contextSet ? 'Context Set' : selectedStudy?.id ? 'Set Context' : 'No Study'}
</button>
{onToggleExpand && (
<button
onClick={onToggleExpand}

View File

@@ -14,9 +14,10 @@ import { useStudy } from '../../context/StudyContext';
interface ControlPanelProps {
onStatusChange?: () => void;
horizontal?: boolean;
}
export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange }) => {
export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange, horizontal = false }) => {
const { selectedStudy, refreshStudies } = useStudy();
const [processStatus, setProcessStatus] = useState<ProcessStatus | null>(null);
const [actionInProgress, setActionInProgress] = useState<string | null>(null);
@@ -131,6 +132,177 @@ export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange }) =>
const isRunning = processStatus?.is_running || selectedStudy?.status === 'running';
// Horizontal layout for top of page
if (horizontal) {
return (
<div className="bg-dark-800 rounded-xl border border-dark-600 overflow-hidden">
<div className="px-4 py-3 flex items-center gap-6">
{/* Status */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
{isRunning ? (
<>
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse" />
<span className="text-green-400 font-medium text-sm">Running</span>
</>
) : (
<>
<div className="w-3 h-3 bg-dark-500 rounded-full" />
<span className="text-dark-400 text-sm">Stopped</span>
</>
)}
</div>
{processStatus?.fea_count && (
<span className="text-xs text-dark-400">
FEA: <span className="text-primary-400">{processStatus.fea_count}</span>
{processStatus.nn_count && (
<> | NN: <span className="text-orange-400">{processStatus.nn_count}</span></>
)}
</span>
)}
</div>
{/* Main Actions */}
<div className="flex items-center gap-2">
{isRunning ? (
<button
onClick={handleStop}
disabled={actionInProgress !== null}
className="flex items-center gap-2 px-3 py-1.5 bg-red-600 hover:bg-red-500
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
>
{actionInProgress === 'stop' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Skull className="w-4 h-4" />}
Kill
</button>
) : (
<button
onClick={handleStart}
disabled={actionInProgress !== null}
className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-500
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
>
{actionInProgress === 'start' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
Start
</button>
)}
<button
onClick={handleValidate}
disabled={actionInProgress !== null || isRunning}
className="flex items-center gap-2 px-3 py-1.5 bg-primary-600 hover:bg-primary-500
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
>
{actionInProgress === 'validate' ? <Loader2 className="w-4 h-4 animate-spin" /> : <CheckCircle className="w-4 h-4" />}
Validate
</button>
<button
onClick={handleLaunchOptuna}
disabled={actionInProgress !== null}
className="flex items-center gap-2 px-3 py-1.5 bg-dark-700 hover:bg-dark-600
border border-dark-600 disabled:opacity-50 text-dark-300 hover:text-white rounded-lg text-sm"
>
<ExternalLink className="w-4 h-4" />
Optuna
</button>
</div>
{/* Settings Toggle */}
<button
onClick={() => setShowSettings(!showSettings)}
className={`p-1.5 rounded-lg transition-colors ${
showSettings ? 'bg-primary-600 text-white' : 'bg-dark-700 text-dark-300 hover:text-white'
}`}
>
<Settings className="w-4 h-4" />
</button>
{/* Error */}
{error && (
<div className="flex items-center gap-2 text-red-400 text-sm">
<AlertTriangle className="w-4 h-4" />
{error}
</div>
)}
</div>
{/* Collapsible Settings */}
{showSettings && (
<div className="px-4 py-3 border-t border-dark-700 bg-dark-750">
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">Iterations:</label>
<input
type="number"
value={settings.maxIterations}
onChange={(e) => setSettings({ ...settings, maxIterations: parseInt(e.target.value) || 100 })}
className="w-16 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">FEA Batch:</label>
<input
type="number"
value={settings.feaBatchSize}
onChange={(e) => setSettings({ ...settings, feaBatchSize: parseInt(e.target.value) || 5 })}
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">Patience:</label>
<input
type="number"
value={settings.patience}
onChange={(e) => setSettings({ ...settings, patience: parseInt(e.target.value) || 5 })}
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">Tune:</label>
<input
type="number"
value={settings.tuneTrials}
onChange={(e) => setSettings({ ...settings, tuneTrials: parseInt(e.target.value) || 30 })}
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">Ensemble:</label>
<input
type="number"
value={settings.ensembleSize}
onChange={(e) => setSettings({ ...settings, ensembleSize: parseInt(e.target.value) || 3 })}
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-xs text-dark-400">Validate Top:</label>
<input
type="number"
min={1}
max={20}
value={validateTopN}
onChange={(e) => setValidateTopN(parseInt(e.target.value) || 5)}
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
/>
</div>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={settings.freshStart}
onChange={(e) => setSettings({ ...settings, freshStart: e.target.checked })}
className="w-4 h-4 rounded border-dark-600 bg-dark-700 text-primary-600"
/>
<span className="text-xs text-dark-300">Fresh Start</span>
</label>
</div>
</div>
)}
</div>
);
}
// Vertical layout (original sidebar layout)
return (
<div className="bg-dark-800 rounded-xl border border-dark-600 overflow-hidden">
{/* Header */}

View File

@@ -1,15 +1,10 @@
import { useState, useEffect, lazy, Suspense } from 'react';
import { useNavigate } from 'react-router-dom';
import {
LineChart, Line, ScatterChart, Scatter,
XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell
} from 'recharts';
import { Terminal } from 'lucide-react';
import { useOptimizationWebSocket } from '../hooks/useWebSocket';
import { apiClient } from '../api/client';
import { useStudy } from '../context/StudyContext';
import { Card } from '../components/common/Card';
import { MetricCard } from '../components/dashboard/MetricCard';
import { ControlPanel } from '../components/dashboard/ControlPanel';
import { ClaudeTerminal } from '../components/ClaudeTerminal';
import { ParetoPlot } from '../components/ParetoPlot';
@@ -19,7 +14,7 @@ import { ConvergencePlot } from '../components/ConvergencePlot';
import { StudyReportViewer } from '../components/StudyReportViewer';
import { ConsoleOutput } from '../components/ConsoleOutput';
import { ExpandableChart } from '../components/ExpandableChart';
import type { Trial, ConvergenceDataPoint, ParameterSpaceDataPoint } from '../types';
import type { Trial } from '../types';
// Lazy load Plotly components for better initial load performance
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
@@ -417,68 +412,62 @@ export default function Dashboard() {
</div>
</header>
<div className="grid grid-cols-12 gap-4">
{/* Control Panel - Left Sidebar (smaller) */}
<aside className="col-span-2">
<ControlPanel onStatusChange={refreshStudies} />
</aside>
{/* Control Panel - Full Width on Top */}
<div className="mb-6">
<ControlPanel onStatusChange={refreshStudies} horizontal />
</div>
{/* Main Content - takes most of the space */}
<main className={chatOpen ? 'col-span-6' : 'col-span-10'}>
{/* Study Name Header */}
{selectedStudyId && (
<div className="mb-4 pb-3 border-b border-dark-600">
<h2 className="text-xl font-semibold text-primary-300">
{selectedStudyId}
</h2>
{studyMetadata?.description && (
<p className="text-sm text-dark-400 mt-1">{studyMetadata.description}</p>
{/* Main Layout: Charts + Claude Terminal */}
<div className={`grid gap-4 ${chatOpen ? 'grid-cols-12' : 'grid-cols-1'}`}>
{/* Main Content - Charts stacked vertically */}
<main className={chatOpen ? 'col-span-7' : 'col-span-1'}>
{/* Study Name Header + Metrics in one row */}
<div className="mb-4 pb-3 border-b border-dark-600 flex items-center justify-between">
<div>
{selectedStudyId && (
<>
<h2 className="text-xl font-semibold text-primary-300">
{selectedStudyId}
</h2>
{studyMetadata?.description && (
<p className="text-sm text-dark-400 mt-1">{studyMetadata.description}</p>
)}
</>
)}
</div>
)}
{/* Metrics Grid */}
<div className="grid grid-cols-4 gap-4 mb-6">
<MetricCard label="Total Trials" value={allTrials.length} />
<MetricCard
label="Best Value"
value={bestValue === Infinity ? '-' : bestValue.toFixed(4)}
valueColor="text-green-400"
/>
<MetricCard
label="Avg Objective"
value={avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
valueColor="text-blue-400"
/>
<MetricCard
label="Pruned"
value={prunedCount}
valueColor={prunedCount > 0 ? 'text-red-400' : 'text-green-400'}
/>
{/* Compact Metrics */}
<div className="flex gap-3">
<div className="text-center px-3">
<div className="text-2xl font-bold text-white">{allTrials.length}</div>
<div className="text-xs text-dark-400">Trials</div>
</div>
<div className="text-center px-3 border-l border-dark-600">
<div className="text-2xl font-bold text-green-400">
{bestValue === Infinity ? '-' : bestValue.toFixed(4)}
</div>
<div className="text-xs text-dark-400">Best</div>
</div>
<div className="text-center px-3 border-l border-dark-600">
<div className="text-2xl font-bold text-blue-400">
{avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
</div>
<div className="text-xs text-dark-400">Avg</div>
</div>
<div className="text-center px-3 border-l border-dark-600">
<div className={`text-2xl font-bold ${prunedCount > 0 ? 'text-red-400' : 'text-green-400'}`}>
{prunedCount}
</div>
<div className="text-xs text-dark-400">Pruned</div>
</div>
</div>
</div>
{/* Protocol 13: Intelligent Optimizer & Pareto Front */}
{/* Pareto Front - Full Width */}
{selectedStudyId && paretoFront.length > 0 && studyMetadata && studyMetadata.objectives && (
<div className="grid grid-cols-2 gap-6 mb-6">
<Card title="Optimizer Strategy">
<div className="space-y-2">
<div className="text-sm text-dark-300">
<span className="font-semibold text-dark-100">Algorithm:</span> {studyMetadata.sampler || 'NSGA-II'}
</div>
<div className="text-sm text-dark-300">
<span className="font-semibold text-dark-100">Type:</span> Multi-objective
</div>
<div className="text-sm text-dark-300">
<span className="font-semibold text-dark-100">Objectives:</span> {studyMetadata.objectives?.length || 2}
</div>
<div className="text-sm text-dark-300">
<span className="font-semibold text-dark-100">Design Variables:</span> {studyMetadata.design_variables?.length || 0}
</div>
</div>
</Card>
<div className="mb-4">
<ExpandableChart
title="Pareto Front"
subtitle={`${paretoFront.length} Pareto-optimal solutions`}
subtitle={`${paretoFront.length} Pareto-optimal solutions | ${studyMetadata.sampler || 'NSGA-II'} | ${studyMetadata.objectives?.length || 2} objectives`}
>
{chartLibrary === 'plotly' ? (
<Suspense fallback={<ChartLoading />}>
@@ -486,7 +475,7 @@ export default function Dashboard() {
trials={allTrialsRaw}
paretoFront={paretoFront}
objectives={studyMetadata.objectives}
height={350}
height={300}
/>
</Suspense>
) : (
@@ -500,11 +489,11 @@ export default function Dashboard() {
</div>
)}
{/* Parallel Coordinates (full width for multi-objective) */}
{/* Parallel Coordinates - Full Width */}
{allTrialsRaw.length > 0 && studyMetadata && studyMetadata.objectives && studyMetadata.design_variables && (
<div className="mb-6">
<div className="mb-4">
<ExpandableChart
title="Parallel Coordinates Plot"
title="Parallel Coordinates"
subtitle={`${allTrialsRaw.length} trials - Design Variables → Objectives`}
>
{chartLibrary === 'plotly' ? (
@@ -514,7 +503,7 @@ export default function Dashboard() {
objectives={studyMetadata.objectives}
designVariables={studyMetadata.design_variables}
paretoFront={paretoFront}
height={450}
height={350}
/>
</Suspense>
) : (
@@ -531,9 +520,9 @@ export default function Dashboard() {
{/* Convergence Plot - Full Width */}
{allTrialsRaw.length > 0 && (
<div className="mb-6">
<div className="mb-4">
<ExpandableChart
title="Convergence Plot"
title="Convergence"
subtitle={`Best ${studyMetadata?.objectives?.[0]?.name || 'Objective'} over ${allTrialsRaw.length} trials`}
>
{chartLibrary === 'plotly' ? (
@@ -543,7 +532,7 @@ export default function Dashboard() {
objectiveIndex={0}
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
direction="minimize"
height={350}
height={280}
/>
</Suspense>
) : (
@@ -560,7 +549,7 @@ export default function Dashboard() {
{/* Parameter Importance - Full Width */}
{allTrialsRaw.length > 0 && (studyMetadata?.design_variables?.length > 0 || (allTrialsRaw[0]?.params && Object.keys(allTrialsRaw[0].params).length > 0)) && (
<div className="mb-6">
<div className="mb-4">
<ExpandableChart
title="Parameter Importance"
subtitle={`Correlation with ${studyMetadata?.objectives?.[0]?.name || 'Objective'}`}
@@ -576,7 +565,7 @@ export default function Dashboard() {
}
objectiveIndex={0}
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
height={350}
height={280}
/>
</Suspense>
) : (
@@ -595,138 +584,6 @@ export default function Dashboard() {
</div>
)}
{/* Charts */}
<div className="grid grid-cols-2 gap-6 mb-6">
{/* Convergence Chart */}
<ExpandableChart
title="Convergence Plot (Single Objective)"
subtitle={`${convergenceData.length} trials`}
>
<Card title="Convergence Plot">
{convergenceData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={convergenceData}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis
dataKey="trial_number"
stroke="#94a3b8"
label={{ value: 'Trial Number', position: 'insideBottom', offset: -5, fill: '#94a3b8' }}
/>
<YAxis
stroke="#94a3b8"
label={{ value: 'Objective', angle: -90, position: 'insideLeft', fill: '#94a3b8' }}
/>
<Tooltip
contentStyle={{ backgroundColor: '#1e293b', border: 'none', borderRadius: '8px' }}
labelStyle={{ color: '#e2e8f0' }}
/>
<Legend />
<Line
type="monotone"
dataKey="objective"
stroke="#60a5fa"
name="Objective"
dot={{ r: 3 }}
/>
<Line
type="monotone"
dataKey="best_so_far"
stroke="#10b981"
name="Best So Far"
strokeWidth={2}
dot={{ r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
) : (
<div className="h-64 flex items-center justify-center text-dark-300">
No trial data yet
</div>
)}
</Card>
</ExpandableChart>
{/* Parameter Space Chart with Selectable Axes */}
<ExpandableChart
title="Parameter Space"
subtitle={`${parameterSpaceData.length} trials - ${paramNames[paramXIndex] || 'X'} vs ${paramNames[paramYIndex] || 'Y'}`}
>
<Card title={
<div className="flex items-center justify-between w-full">
<span>Parameter Space</span>
{paramNames.length > 2 && (
<div className="flex items-center gap-2 text-sm">
<span className="text-dark-400">X:</span>
<select
value={paramXIndex}
onChange={(e) => setParamXIndex(Number(e.target.value))}
className="bg-dark-600 text-dark-100 px-2 py-1 rounded text-xs border border-dark-500"
>
{paramNames.map((name, idx) => (
<option key={idx} value={idx}>{name}</option>
))}
</select>
<span className="text-dark-400">Y:</span>
<select
value={paramYIndex}
onChange={(e) => setParamYIndex(Number(e.target.value))}
className="bg-dark-600 text-dark-100 px-2 py-1 rounded text-xs border border-dark-500"
>
{paramNames.map((name, idx) => (
<option key={idx} value={idx}>{name}</option>
))}
</select>
</div>
)}
</div>
}>
{parameterSpaceData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<ScatterChart>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis
type="number"
dataKey="x"
stroke="#94a3b8"
name={paramNames[paramXIndex] || 'X'}
label={{ value: paramNames[paramXIndex] || 'Parameter X', position: 'insideBottom', offset: -5, fill: '#94a3b8' }}
/>
<YAxis
type="number"
dataKey="y"
stroke="#94a3b8"
name={paramNames[paramYIndex] || 'Y'}
label={{ value: paramNames[paramYIndex] || 'Parameter Y', angle: -90, position: 'insideLeft', fill: '#94a3b8' }}
/>
<Tooltip
cursor={{ strokeDasharray: '3 3' }}
contentStyle={{ backgroundColor: '#1e293b', border: 'none', borderRadius: '8px' }}
labelStyle={{ color: '#e2e8f0' }}
formatter={(value: any, name: string) => {
if (name === 'objective') return [value.toFixed(4), 'Objective'];
return [value.toFixed(3), name];
}}
/>
<Scatter name="Trials" data={parameterSpaceData}>
{parameterSpaceData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.isBest ? '#10b981' : '#60a5fa'}
r={entry.isBest ? 8 : 5}
/>
))}
</Scatter>
</ScatterChart>
</ResponsiveContainer>
) : (
<div className="h-64 flex items-center justify-center text-dark-300">
No trial data yet
</div>
)}
</Card>
</ExpandableChart>
</div>
{/* Trial History with Sort Controls and Pagination */}
<Card
title={
@@ -925,18 +782,18 @@ export default function Dashboard() {
</Card>
{/* Console Output - at the bottom */}
<div className="mt-6">
<div className="mt-4">
<ConsoleOutput
studyId={selectedStudyId}
refreshInterval={2000}
maxLines={200}
maxLines={150}
/>
</div>
</main>
{/* Claude Code Terminal - Right Sidebar (taller for better visibility) */}
{/* Claude Code Terminal - Right Sidebar (wider for better visibility) */}
{chatOpen && (
<aside className="col-span-4 h-[calc(100vh-8rem)] sticky top-20">
<aside className="col-span-5 h-[calc(100vh-12rem)] sticky top-4">
<ClaudeTerminal
isExpanded={chatExpanded}
onToggleExpand={() => setChatExpanded(!chatExpanded)}