feat(FN-1149): add send-to-background actions for AI planning modals

- Add Send to background header action to Planning Mode, Subtask Breakdown, and Mission Interview modals using a shared minimize icon pattern
- Show the action only during active AI session states and keep it hidden in initial states
- Implement background behavior to close the live stream connection and modal without canceling the underlying AI session
- Add modal tests that verify button visibility rules and confirm send-to-background does not trigger cancel APIs
- Add shared CSS styling for modal send-to-background controls, including hover and focus-visible states
This commit is contained in:
gsxdsm
2026-04-08 10:15:14 -07:00
parent d3a8e27ef0
commit 33321a3681
7 changed files with 262 additions and 11 deletions

View File

@@ -13,7 +13,7 @@ import {
getSubtaskDescription,
clearSubtaskDescription,
} from "../hooks/modalPersistence";
import { CheckCircle, Loader2, ListTree, Plus, Trash2, X, GripVertical, ArrowUp, ArrowDown } from "lucide-react";
import { CheckCircle, Loader2, ListTree, Plus, Trash2, X, GripVertical, ArrowUp, ArrowDown, Minimize2 } from "lucide-react";
interface SubtaskBreakdownModalProps {
isOpen: boolean;
@@ -91,6 +91,8 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
return hasDependencyCycle(subtasks);
}, [subtasks]);
const showSendToBackgroundButton = view.type === "generating" || view.type === "editing";
const resetState = useCallback(() => {
// Save to localStorage before cleanup (preserve for re-entry)
if (localDescription) {
@@ -108,6 +110,12 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
autoStartedRef.current = false;
}, [localDescription]);
const handleSendToBackground = useCallback(() => {
streamRef.current?.close();
streamRef.current = null;
onClose();
}, [onClose]);
const handleClose = useCallback(async () => {
if ((dirty || view.type === "editing" || view.type === "creating") && !confirm("Close subtask breakdown? Unsaved changes will be lost.")) {
return;
@@ -367,9 +375,21 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
<ListTree size={20} style={{ color: "var(--triage)" }} />
<h3>Subtask Breakdown</h3>
</div>
<button className="modal-close" onClick={() => void handleClose()} aria-label="Close">
<X size={20} />
</button>
<div className="modal-header-actions">
{showSendToBackgroundButton && (
<button
className="modal-send-to-background"
onClick={handleSendToBackground}
title="Send to background"
aria-label="Send to background"
>
<Minimize2 size={16} />
</button>
)}
<button className="modal-close" onClick={() => void handleClose()} aria-label="Close">
<X size={20} />
</button>
</div>
</div>
<div className="planning-modal-body">