feat(FN-1010): add mission start endpoint and UI button

- Add POST /api/missions/:missionId/start backend endpoint to start planning missions
- Add startMission() frontend API function in dashboard api.ts
- Add Start button in MissionManager component for planning missions
- Add integration tests for the startMission endpoint covering success, error, and edge cases
This commit is contained in:
gsxdsm
2026-04-06 00:08:39 -07:00
parent 1c76e25aa8
commit 2eae2834f4
4 changed files with 238 additions and 0 deletions

View File

@@ -2654,6 +2654,13 @@ export function stopMission(missionId: string, projectId?: string): Promise<Miss
});
}
/** Start a planning mission: sets status to "active" and activates the first pending slice */
export function startMission(missionId: string, projectId?: string): Promise<MissionWithHierarchy> {
return api<MissionWithHierarchy>(withProjectId(`/missions/${encodeURIComponent(missionId)}/start`, projectId), {
method: "POST",
});
}
// ── Mission Autopilot API ────────────────────────────────────────────────
/** Fetch autopilot status for a mission */

View File

@@ -61,6 +61,7 @@ import {
pauseMission,
resumeMission,
stopMission,
startMission,
fetchMissionAutopilotStatus,
updateMissionAutopilot,
startMissionAutopilot,
@@ -686,6 +687,18 @@ export function MissionManager({ isOpen, onClose, addToast, projectId, onSelectT
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
// Start a planning mission — set status to "active" and activate first slice
const handleStartMission = useCallback(async (missionId: string) => {
try {
await startMission(missionId, projectId);
addToast("Mission started — first slice activated", "success");
await loadMissionDetail(missionId);
loadMissions();
} catch (err: any) {
addToast(err.message || "Failed to start mission", "error");
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
// ── Autopilot handlers ──
const loadAutopilotStatus = useCallback(async (missionId: string) => {
@@ -980,6 +993,16 @@ export function MissionManager({ isOpen, onClose, addToast, projectId, onSelectT
<RefreshCw size={14} />
</button>
)}
{selectedMission.status === "planning" && (
<button
className="mission-icon-btn mission-icon-btn--success"
onClick={() => handleStartMission(selectedMission.id)}
title="Start mission"
aria-label="Start mission"
>
<Play size={14} />
</button>
)}
<button
className="mission-icon-btn"
onClick={() => handleEditMission(selectedMission)}
@@ -1597,6 +1620,15 @@ export function MissionManager({ isOpen, onClose, addToast, projectId, onSelectT
<RefreshCw size={14} />
</button>
)}
{m.status === "planning" && (
<button
className="mission-icon-btn mission-icon-btn--success"
onClick={() => handleStartMission(m.id)}
title="Start mission"
>
<Play size={14} />
</button>
)}
<button
className="mission-icon-btn"
onClick={() => handleEditMission(mission)}