diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index df8b512..7dd4244 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useCallback } from 'react';
import { CommandConsole } from './components/CommandConsole';
import { LiveView } from './components/LiveView';
import { useMission } from './hooks/useMission';
@@ -11,7 +11,7 @@ function App() {
const { mission, latestScreenshot, error } = useMission(currentMissionId);
- const handleSubmitMission = async (prompt: string) => {
+ const handleSubmitMission = useCallback(async (prompt: string) => {
setIsSubmitting(true);
try {
const response = await api.createMission(prompt);
@@ -22,7 +22,7 @@ function App() {
} finally {
setIsSubmitting(false);
}
- };
+ }, []);
return (
diff --git a/frontend/src/components/CommandConsole.tsx b/frontend/src/components/CommandConsole.tsx
index 07a3dbc..dc5e6b8 100644
--- a/frontend/src/components/CommandConsole.tsx
+++ b/frontend/src/components/CommandConsole.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useCallback, useMemo, memo } from 'react';
import { Mission, MissionStepType, MissionStatus } from '../types/mission';
import './CommandConsole.css';
@@ -8,22 +8,25 @@ interface CommandConsoleProps {
isSubmitting: boolean;
}
-export function CommandConsole({
+export const CommandConsole = memo(function CommandConsole({
mission,
onSubmitMission,
isSubmitting,
}: CommandConsoleProps) {
const [prompt, setPrompt] = useState('');
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- if (prompt.trim() && !isSubmitting) {
- onSubmitMission(prompt.trim());
- setPrompt('');
- }
- };
+ const handleSubmit = useCallback(
+ (e: React.FormEvent) => {
+ e.preventDefault();
+ if (prompt.trim() && !isSubmitting) {
+ onSubmitMission(prompt.trim());
+ setPrompt('');
+ }
+ },
+ [prompt, isSubmitting, onSubmitMission]
+ );
- const getStepIcon = (type: MissionStepType): string => {
+ const getStepIcon = useCallback((type: MissionStepType): string => {
switch (type) {
case MissionStepType.OBSERVATION:
return '👁️';
@@ -36,9 +39,9 @@ export function CommandConsole({
default:
return '•';
}
- };
+ }, []);
- const getStatusBadge = (status: MissionStatus) => {
+ const getStatusBadge = useCallback((status: MissionStatus) => {
const badges = {
[MissionStatus.PENDING]: { label: 'PENDING', className: 'pending' },
[MissionStatus.RUNNING]: { label: 'RUNNING', className: 'running' },
@@ -53,12 +56,22 @@ export function CommandConsole({
const badge = badges[status] || { label: status, className: '' };
return
{badge.label};
- };
+ }, []);
- const formatTimestamp = (timestamp: string): string => {
+ const formatTimestamp = useCallback((timestamp: string): string => {
const date = new Date(timestamp);
return date.toLocaleTimeString();
- };
+ }, []);
+
+ // Memoize sorted/processed steps to avoid recalculation on every render
+ const sortedSteps = useMemo(() => {
+ if (!mission || !mission.steps) return [];
+ return [...mission.steps].sort((a, b) => {
+ const aTime = new Date(a.timestamp).getTime();
+ const bTime = new Date(b.timestamp).getTime();
+ return aTime - bTime;
+ });
+ }, [mission?.steps]);
return (
@@ -100,8 +113,8 @@ export function CommandConsole({
Mission Timeline
- {mission && mission.steps.length > 0 ? (
- mission.steps.map((step) => (
+ {sortedSteps.length > 0 ? (
+ sortedSteps.map((step) => (
{getStepIcon(step.type)}
@@ -126,4 +139,4 @@ export function CommandConsole({
);
-}
+});
diff --git a/frontend/src/components/LiveView.tsx b/frontend/src/components/LiveView.tsx
index 018eb20..1c1f622 100644
--- a/frontend/src/components/LiveView.tsx
+++ b/frontend/src/components/LiveView.tsx
@@ -1,3 +1,4 @@
+import { memo } from 'react';
import { Mission } from '../types/mission';
import './LiveView.css';
@@ -6,7 +7,7 @@ interface LiveViewProps {
latestScreenshot: string | null;
}
-export function LiveView({ mission, latestScreenshot }: LiveViewProps) {
+export const LiveView = memo(function LiveView({ mission, latestScreenshot }: LiveViewProps) {
return (
@@ -79,4 +80,4 @@ export function LiveView({ mission, latestScreenshot }: LiveViewProps) {
)}
);
-}
+});