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

@@ -70,6 +70,19 @@ describe("SubtaskBreakdownModal", () => {
expect(await screen.findByText("AI is generating subtasks...")).toBeInTheDocument();
});
it("hides send to background button in initial state", () => {
render(
<SubtaskBreakdownModal
isOpen={true}
onClose={onClose}
initialDescription=""
onTasksCreated={onTasksCreated}
/>,
);
expect(screen.queryByLabelText("Send to background")).not.toBeInTheDocument();
});
it("renders editable subtasks when stream returns items", async () => {
renderModal();
await waitFor(() => expect(streamHandlers).toBeDefined());
@@ -181,6 +194,24 @@ describe("SubtaskBreakdownModal", () => {
expect(onClose).toHaveBeenCalled();
});
it("sends active breakdown session to background without canceling", async () => {
const closeSpy = vi.fn();
mockConnectSubtaskStream.mockImplementationOnce((_sessionId, _projectId, handlers) => {
streamHandlers = handlers;
return { close: closeSpy, isConnected: () => true };
});
renderModal();
await waitFor(() => expect(mockStartSubtaskBreakdown).toHaveBeenCalled());
await screen.findByText("AI is generating subtasks...");
fireEvent.click(screen.getByLabelText("Send to background"));
expect(closeSpy).toHaveBeenCalledTimes(1);
expect(mockCancelSubtaskBreakdown).not.toHaveBeenCalled();
expect(onClose).toHaveBeenCalledTimes(1);
});
it("cancel closes modal", async () => {
renderModal();
await waitFor(() => expect(mockStartSubtaskBreakdown).toHaveBeenCalled());