feat(FN-4611): add themed quick chat session dropdown

- Replace the quick chat header mode toggle with a session picker dropdown styled from dashboard theme tokens
- Surface chat room entries alongside sessions and keep active room/session switching in the same menu
- Add QuickChatFAB and CSS regression coverage for the new dropdown behavior and token-based styling
- Add a changeset note for the quick chat session picker update

Fusion-Task-Id: FN-4611
Fusion-Task-Id: FN-4634
Fusion-Task-Lineage: 87cc75e7-ba50-4cb5-bb9f-6d86f49268e0
This commit is contained in:
Fusion
2026-05-15 09:39:30 -07:00
committed by gsxdsm
parent 4d99a72c94
commit ea9ec50a1d
5 changed files with 340 additions and 24 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
QuickChat session picker now uses the themed dropdown style and includes chat rooms in the switch list.

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { loadAllAppCss } from "../test/cssFixture";
describe("QuickChat session dropdown CSS", () => {
it("defines themed dropdown selectors using tokens", async () => {
const css = await loadAllAppCss();
const triggerBlock = css.match(/\.quick-chat-session-trigger\s*\{[^}]*\}/)?.[0] ?? "";
const dropdownBlock = css.match(/\.quick-chat-session-dropdown\s*\{[^}]*\}/)?.[0] ?? "";
expect(triggerBlock).toContain(".quick-chat-session-trigger");
expect(dropdownBlock).toContain(".quick-chat-session-dropdown");
const combined = `${triggerBlock}\n${dropdownBlock}`;
expect(combined).not.toMatch(/#[0-9a-fA-F]{3,8}\b/);
expect(combined).not.toMatch(/rgba?\(/);
expect(combined).not.toMatch(/(?<![\w-])(?:[1-9]\d*|\d+\.\d+)px\b/);
});
});

View File

@@ -381,6 +381,91 @@
padding: calc(var(--space-sm) - (var(--space-xs) / 4)) calc(var(--space-md) - (var(--space-xs) / 4)); padding: calc(var(--space-sm) - (var(--space-xs) / 4)) calc(var(--space-md) - (var(--space-xs) / 4));
} }
.quick-chat-session-menu {
position: relative;
}
.quick-chat-session-trigger {
width: 100%;
justify-content: flex-start;
gap: var(--space-sm);
background: var(--surface);
color: var(--text);
border-color: var(--border);
border-radius: var(--radius-md);
padding: var(--space-sm);
transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.quick-chat-session-trigger > span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quick-chat-session-trigger > svg:last-child {
margin-left: auto;
color: var(--text-muted);
}
.quick-chat-session-trigger:hover {
background: var(--card-hover);
}
.quick-chat-session-trigger:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.quick-chat-session-dropdown {
position: absolute;
top: calc(100% + var(--space-xs));
left: 0;
width: 100%;
max-height: calc(var(--space-xl) * 10);
overflow-y: auto;
background: var(--surface);
border: var(--btn-border-width) solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
padding: var(--space-xs);
z-index: 4;
}
.quick-chat-session-dropdown-group-label {
margin: var(--space-xs) var(--space-sm);
color: var(--text-muted);
font-size: var(--space-sm);
font-weight: 600;
letter-spacing: calc(var(--space-xs) / 8);
text-transform: uppercase;
}
.quick-chat-session-option {
width: 100%;
border: none;
background: transparent;
color: var(--text);
border-radius: var(--radius-sm);
padding: var(--space-sm);
text-align: left;
transition: background var(--transition-fast), color var(--transition-fast);
}
.quick-chat-session-option:hover {
background: var(--card-hover);
}
.quick-chat-session-option:focus-visible {
outline: none;
box-shadow: var(--focus-ring-strong);
}
.quick-chat-session-option--active {
background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.quick-chat-panel-messages { .quick-chat-panel-messages {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -791,6 +876,13 @@
z-index: 1100; z-index: 1100;
} }
.quick-chat-session-dropdown {
left: 0;
right: 0;
width: auto;
max-height: calc(100vh - var(--header-height) - var(--mobile-nav-height));
}
.quick-chat-resize-handle { .quick-chat-resize-handle {
display: none; display: none;
} }

View File

@@ -26,6 +26,8 @@ import { FileMentionPopup } from "./FileMentionPopup";
import { useFileMention } from "../hooks/useFileMention"; import { useFileMention } from "../hooks/useFileMention";
import { useMobileKeyboard } from "../hooks/useMobileKeyboard"; import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
import { useViewportMode } from "../hooks/useViewportMode"; import { useViewportMode } from "../hooks/useViewportMode";
import { useAppSettings } from "../hooks/useAppSettings";
import { useChatRooms } from "../hooks/useChatRooms";
interface PendingAttachment { interface PendingAttachment {
file: File; file: File;
@@ -893,6 +895,7 @@ export function QuickChatFAB({
const [chatMode, setChatMode] = useState<"agent" | "model">("agent"); const [chatMode, setChatMode] = useState<"agent" | "model">("agent");
const [selectedAgentId, setSelectedAgentId] = useState<string>(""); const [selectedAgentId, setSelectedAgentId] = useState<string>("");
const [newSessionChooserOpen, setNewSessionChooserOpen] = useState(false); const [newSessionChooserOpen, setNewSessionChooserOpen] = useState(false);
const [sessionMenuOpen, setSessionMenuOpen] = useState(false);
const [newSessionMode, setNewSessionMode] = useState<"agent" | "model">("model"); const [newSessionMode, setNewSessionMode] = useState<"agent" | "model">("model");
const [newSessionAgentId, setNewSessionAgentId] = useState<string>(""); const [newSessionAgentId, setNewSessionAgentId] = useState<string>("");
const [newSessionModel, setNewSessionModel] = useState<string>(""); const [newSessionModel, setNewSessionModel] = useState<string>("");
@@ -985,11 +988,15 @@ export function QuickChatFAB({
refreshSessions, refreshSessions,
skipNextSessionInitRef, skipNextSessionInitRef,
} = useQuickChat(projectId, addToast); } = useQuickChat(projectId, addToast);
const { experimentalFeatures } = useAppSettings();
const chatRoomsEnabled = experimentalFeatures?.chatRooms === true;
const roomsState = useChatRooms(projectId, addToast);
const panelRef = useRef<HTMLDivElement | null>(null); const panelRef = useRef<HTMLDivElement | null>(null);
const fabRef = useRef<HTMLButtonElement | null>(null); const fabRef = useRef<HTMLButtonElement | null>(null);
const messagesRef = useRef<HTMLDivElement | null>(null); const messagesRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLTextAreaElement | null>(null); const inputRef = useRef<HTMLTextAreaElement | null>(null);
const sessionMenuRef = useRef<HTMLDivElement | null>(null);
const fileInputRef = useRef<HTMLInputElement | null>(null); const fileInputRef = useRef<HTMLInputElement | null>(null);
const pendingAttachmentsRef = useRef<PendingAttachment[]>([]); const pendingAttachmentsRef = useRef<PendingAttachment[]>([]);
const shouldAutoFocusComposerRef = useRef(false); const shouldAutoFocusComposerRef = useRef(false);
@@ -1648,6 +1655,60 @@ export function QuickChatFAB({
}); });
}, [agents, models, sessions]); }, [agents, models, sessions]);
const roomOptions = useMemo(
() => (chatRoomsEnabled ? roomsState.rooms : []),
[chatRoomsEnabled, roomsState.rooms],
);
const showRoomGroups = chatRoomsEnabled && roomOptions.length > 0;
const activeSessionLabel = useMemo(() => {
if (showRoomGroups && roomsState.activeRoom) {
return `#${roomsState.activeRoom.name}`;
}
const activeOption = sessionOptions.find((option) => option.id === activeSession?.id);
if (activeOption) {
return activeOption.label;
}
if (sessionsLoading) {
return "Loading sessions…";
}
return "Select a session";
}, [activeSession?.id, roomsState.activeRoom, sessionOptions, sessionsLoading, showRoomGroups]);
useEffect(() => {
if (!isOpen) {
setSessionMenuOpen(false);
}
}, [isOpen]);
useEffect(() => {
if (!sessionMenuOpen) {
return;
}
const handleSessionMenuOutsideClick = (event: MouseEvent) => {
const target = event.target as Node;
if (sessionMenuRef.current?.contains(target)) {
return;
}
setSessionMenuOpen(false);
};
const handleSessionMenuEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setSessionMenuOpen(false);
}
};
document.addEventListener("mousedown", handleSessionMenuOutsideClick);
document.addEventListener("keydown", handleSessionMenuEscape);
return () => {
document.removeEventListener("mousedown", handleSessionMenuOutsideClick);
document.removeEventListener("keydown", handleSessionMenuEscape);
};
}, [sessionMenuOpen]);
const inputPlaceholder = useMemo(() => { const inputPlaceholder = useMemo(() => {
if (chatMode === "agent") { if (chatMode === "agent") {
if (selectedAgent) { if (selectedAgent) {
@@ -1679,8 +1740,14 @@ export function QuickChatFAB({
} }
void selectSession(selectedSession); void selectSession(selectedSession);
setSessionMenuOpen(false);
}, [selectSession, sessions]); }, [selectSession, sessions]);
const handleRoomSwitch = useCallback((roomId: string) => {
roomsState.selectRoom(roomId);
setSessionMenuOpen(false);
}, [roomsState]);
const handleCreateFreshSession = useCallback(async () => { const handleCreateFreshSession = useCallback(async () => {
if (sessionsLoading) return; if (sessionsLoading) return;
@@ -2405,18 +2472,65 @@ export function QuickChatFAB({
</div> </div>
<div className="quick-chat-panel-agent-select" data-testid="quick-chat-session-select"> <div className="quick-chat-panel-agent-select" data-testid="quick-chat-session-select">
<label htmlFor="quick-chat-session-select" className="visually-hidden">Select session</label> <div className="quick-chat-session-menu" ref={sessionMenuRef}>
<select <label htmlFor="quick-chat-session-dropdown-trigger" className="visually-hidden">Select session</label>
id="quick-chat-session-select" <input type="hidden" data-testid="quick-chat-session-dropdown" value={activeSession?.id ?? ""} readOnly />
value={activeSession?.id ?? ""} <button
onChange={(event) => handleSessionSwitch(event.target.value)} id="quick-chat-session-dropdown-trigger"
data-testid="quick-chat-session-dropdown" type="button"
className="btn quick-chat-session-trigger"
aria-haspopup="menu"
aria-expanded={sessionMenuOpen}
data-testid="quick-chat-session-dropdown-trigger"
onClick={() => setSessionMenuOpen((current) => !current)}
> >
<option value="" disabled>{sessionsLoading ? "Loading sessions…" : "Select a session"}</option> {activeSession?.modelProvider ? (
<ProviderIcon provider={activeSession.modelProvider} size="sm" />
) : (
<MessageSquare size={16} aria-hidden="true" />
)}
<span>{activeSessionLabel}</span>
<ChevronDown size={16} aria-hidden="true" />
</button>
{sessionMenuOpen && (
<div className="quick-chat-session-dropdown" role="menu" data-testid="quick-chat-session-dropdown-menu">
{showRoomGroups && (
<div className="quick-chat-session-dropdown-group-label">Sessions</div>
)}
{sessionOptions.map((sessionOption) => ( {sessionOptions.map((sessionOption) => (
<option key={sessionOption.id} value={sessionOption.id}>{sessionOption.label}</option> <button
key={sessionOption.id}
type="button"
role="menuitem"
data-testid={`quick-chat-session-option-${sessionOption.id}`}
className={`quick-chat-session-option${roomsState.activeRoom === null && activeSession?.id === sessionOption.id ? " quick-chat-session-option--active" : ""}`}
onClick={() => handleSessionSwitch(sessionOption.id)}
>
{sessionOption.label}
</button>
))} ))}
</select>
{showRoomGroups && (
<>
<div className="quick-chat-session-dropdown-group-label">Rooms</div>
{roomOptions.map((room) => (
<button
key={room.id}
type="button"
role="menuitem"
data-testid={`quick-chat-session-option-room-${room.slug}`}
className={`quick-chat-session-option${roomsState.activeRoom?.id === room.id ? " quick-chat-session-option--active" : ""}`}
onClick={() => handleRoomSwitch(room.id)}
>
#{room.name}
</button>
))}
</>
)}
</div>
)}
</div>
</div> </div>
{newSessionChooserOpen && ( {newSessionChooserOpen && (

View File

@@ -6,6 +6,8 @@ import * as apiModule from "../../api";
import { useAgents } from "../../hooks/useAgents"; import { useAgents } from "../../hooks/useAgents";
import { useViewportMode } from "../../hooks/useViewportMode"; import { useViewportMode } from "../../hooks/useViewportMode";
import { useMobileKeyboard } from "../../hooks/useMobileKeyboard"; import { useMobileKeyboard } from "../../hooks/useMobileKeyboard";
import { useAppSettings } from "../../hooks/useAppSettings";
import { useChatRooms } from "../../hooks/useChatRooms";
import { QuickChatFAB } from "../QuickChatFAB"; import { QuickChatFAB } from "../QuickChatFAB";
vi.mock("../../api", () => ({ vi.mock("../../api", () => ({
@@ -24,6 +26,8 @@ vi.mock("../../api", () => ({
vi.mock("../../hooks/useAgents", () => ({ useAgents: vi.fn() })); vi.mock("../../hooks/useAgents", () => ({ useAgents: vi.fn() }));
vi.mock("../../hooks/useViewportMode", () => ({ useViewportMode: vi.fn() })); vi.mock("../../hooks/useViewportMode", () => ({ useViewportMode: vi.fn() }));
vi.mock("../../hooks/useMobileKeyboard", () => ({ useMobileKeyboard: vi.fn() })); vi.mock("../../hooks/useMobileKeyboard", () => ({ useMobileKeyboard: vi.fn() }));
vi.mock("../../hooks/useAppSettings", () => ({ useAppSettings: vi.fn() }));
vi.mock("../../hooks/useChatRooms", () => ({ useChatRooms: vi.fn() }));
const mockFetchResumeChatSession = vi.mocked(apiModule.fetchResumeChatSession); const mockFetchResumeChatSession = vi.mocked(apiModule.fetchResumeChatSession);
const mockFetchChatSessions = vi.mocked(apiModule.fetchChatSessions); const mockFetchChatSessions = vi.mocked(apiModule.fetchChatSessions);
@@ -36,6 +40,8 @@ const mockCancelChatResponse = vi.mocked(apiModule.cancelChatResponse);
const mockUseAgents = vi.mocked(useAgents); const mockUseAgents = vi.mocked(useAgents);
const mockUseViewportMode = vi.mocked(useViewportMode); const mockUseViewportMode = vi.mocked(useViewportMode);
const mockUseMobileKeyboard = vi.mocked(useMobileKeyboard); const mockUseMobileKeyboard = vi.mocked(useMobileKeyboard);
const mockUseAppSettings = vi.mocked(useAppSettings);
const mockUseChatRooms = vi.mocked(useChatRooms);
const agents: Agent[] = [ const agents: Agent[] = [
{ id: "agent-001", name: "Agent One", role: "executor", state: "active", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {} }, { id: "agent-001", name: "Agent One", role: "executor", state: "active", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {} },
@@ -117,6 +123,23 @@ describe("QuickChatFAB session-first UX", () => {
viewportOffsetTop: 0, viewportOffsetTop: 0,
keyboardOpen: false, keyboardOpen: false,
}); });
mockUseAppSettings.mockReturnValue({
experimentalFeatures: {},
} as ReturnType<typeof useAppSettings>);
mockUseChatRooms.mockReturnValue({
rooms: [],
roomsLoading: false,
roomsError: null,
activeRoom: null,
activeRoomMembers: [],
messages: [],
messagesLoading: false,
selectRoom: vi.fn(),
createRoom: vi.fn(),
deleteRoom: vi.fn(),
sendRoomMessage: vi.fn(),
refreshRooms: vi.fn(),
});
mockFetchResumeChatSession.mockImplementation(async ({ agentId, modelProvider, modelId }) => ({ mockFetchResumeChatSession.mockImplementation(async ({ agentId, modelProvider, modelId }) => ({
session: resolveResumeSession(agentId, modelProvider, modelId), session: resolveResumeSession(agentId, modelProvider, modelId),
})); }));
@@ -150,8 +173,64 @@ describe("QuickChatFAB session-first UX", () => {
expect(await screen.findByTestId("quick-chat-session-dropdown")).toBeInTheDocument(); expect(await screen.findByTestId("quick-chat-session-dropdown")).toBeInTheDocument();
expect(screen.queryByTestId("quick-chat-mode-toggle")).toBeNull(); expect(screen.queryByTestId("quick-chat-mode-toggle")).toBeNull();
expect(screen.getByRole("option", { name: "Model thread — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument(); fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
expect(screen.getByRole("option", { name: "Session 2 — Agent One" })).toBeInTheDocument(); expect(screen.getByTestId("quick-chat-session-option-session-model")).toHaveClass("quick-chat-session-option--active");
expect(screen.getByTestId("quick-chat-session-option-session-agent")).toBeInTheDocument();
});
it("does not render room options when chat rooms are disabled", async () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab"));
fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
expect(screen.queryByTestId("quick-chat-session-option-room-engineering")).toBeNull();
});
it("opens/closes session menu and handles rooms when enabled", async () => {
const selectRoom = vi.fn();
mockUseAppSettings.mockReturnValue({ experimentalFeatures: { chatRooms: true } } as ReturnType<typeof useAppSettings>);
mockUseChatRooms.mockReturnValue({
rooms: [{ id: "room-1", name: "engineering", slug: "engineering", memberCount: 2, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }],
roomsLoading: false,
roomsError: null,
activeRoom: { id: "room-1", name: "engineering", slug: "engineering", memberCount: 2, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() },
activeRoomMembers: [],
messages: [],
messagesLoading: false,
selectRoom,
createRoom: vi.fn(),
deleteRoom: vi.fn(),
sendRoomMessage: vi.fn(),
refreshRooms: vi.fn(),
});
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab"));
const trigger = await screen.findByTestId("quick-chat-session-dropdown-trigger");
expect(trigger).toHaveTextContent("#engineering");
fireEvent.click(trigger);
expect(screen.getByTestId("quick-chat-session-dropdown-menu")).toBeInTheDocument();
expect(screen.getByText("Sessions")).toBeInTheDocument();
expect(screen.getByText("Rooms")).toBeInTheDocument();
expect(screen.getByTestId("quick-chat-session-option-session-model")).toHaveClass("quick-chat-session-option");
expect(screen.getByTestId("quick-chat-session-option-room-engineering")).toHaveClass("quick-chat-session-option--active");
fireEvent.click(screen.getByTestId("quick-chat-session-option-room-engineering"));
expect(selectRoom).toHaveBeenCalledWith("room-1");
fireEvent.click(trigger);
fireEvent.mouseDown(screen.getByTestId("quick-chat-new-thread"));
await waitFor(() => {
expect(screen.queryByTestId("quick-chat-session-dropdown-menu")).toBeNull();
});
fireEvent.click(trigger);
fireEvent.click(screen.getByTestId("quick-chat-session-option-session-model"));
await waitFor(() => {
expect(mockFetchChatMessages).toHaveBeenCalledWith("session-model", { limit: 50 }, "proj-1");
});
}); });
it("opens inline chooser from new button defaulting to model", async () => { it("opens inline chooser from new button defaulting to model", async () => {
@@ -218,7 +297,8 @@ describe("QuickChatFAB session-first UX", () => {
expect(screen.getByTestId("quick-chat-input")).toHaveAttribute("placeholder", "Message Agent Two"); expect(screen.getByTestId("quick-chat-input")).toHaveAttribute("placeholder", "Message Agent Two");
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest"); expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest");
}); });
expect(screen.getByRole("option", { name: /Claude thread/i })).toBeInTheDocument(); fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
expect(screen.getByTestId("quick-chat-session-option-archived-newest")).toBeInTheDocument();
}); });
it("reopen restores the newest active session by max(lastMessageAt, updatedAt)", async () => { it("reopen restores the newest active session by max(lastMessageAt, updatedAt)", async () => {
@@ -247,7 +327,8 @@ describe("QuickChatFAB session-first UX", () => {
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("newer-last-message"); expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("newer-last-message");
}); });
fireEvent.change(screen.getByTestId("quick-chat-session-dropdown"), { target: { value: "older-updated" } }); fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
fireEvent.click(screen.getByTestId("quick-chat-session-option-older-updated"));
await waitFor(() => { await waitFor(() => {
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("older-updated"); expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("older-updated");
}); });
@@ -293,7 +374,8 @@ describe("QuickChatFAB session-first UX", () => {
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest"); expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest");
}); });
fireEvent.change(screen.getByTestId("quick-chat-session-dropdown"), { target: { value: "active-older" } }); fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
fireEvent.click(screen.getByTestId("quick-chat-session-option-active-older"));
await waitFor(() => { await waitFor(() => {
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-older"); expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-older");
}); });
@@ -372,15 +454,17 @@ describe("QuickChatFAB session-first UX", () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />); render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab")); fireEvent.click(screen.getByTestId("quick-chat-fab"));
expect(await screen.findByRole("option", { name: "Session 1 — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument(); fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
expect(screen.getByRole("option", { name: "Session 2 — Claude 3.7 Sonnet [anthropic/claude-3-7-sonnet]" })).toBeInTheDocument(); expect(screen.getByTestId("quick-chat-session-option-session-openai")).toBeInTheDocument();
expect(screen.getByTestId("quick-chat-session-option-session-anthropic")).toBeInTheDocument();
}); });
it("includes both title and model descriptor in session label", async () => { it("includes both title and model descriptor in session label", async () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />); render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab")); fireEvent.click(screen.getByTestId("quick-chat-fab"));
expect(await screen.findByRole("option", { name: "Model thread — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument(); fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
expect(screen.getByTestId("quick-chat-session-option-session-model")).toBeInTheDocument();
}); });
it("uses icon-only model tag without pill styling when mobile header fallback is active", async () => { it("uses icon-only model tag without pill styling when mobile header fallback is active", async () => {
@@ -577,9 +661,9 @@ describe("QuickChatFAB session-first UX", () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />); render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab")); fireEvent.click(screen.getByTestId("quick-chat-fab"));
const select = await screen.findByTestId("quick-chat-session-dropdown"); await screen.findByTestId("quick-chat-session-dropdown");
await screen.findByRole("option", { name: "Session 2 — Agent One" }); fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
fireEvent.change(select, { target: { value: "session-agent" } }); fireEvent.click(screen.getByTestId("quick-chat-session-option-session-agent"));
await waitFor(() => { await waitFor(() => {
expect(mockFetchChatMessages).toHaveBeenCalledWith("session-agent", { limit: 50 }, "proj-1"); expect(mockFetchChatMessages).toHaveBeenCalledWith("session-agent", { limit: 50 }, "proj-1");
@@ -970,7 +1054,9 @@ describe("QuickChatFAB session-first UX", () => {
}, },
}); });
fireEvent.change(await screen.findByTestId("quick-chat-session-dropdown"), { target: { value: "session-agent" } }); await screen.findByTestId("quick-chat-session-dropdown");
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
fireEvent.click(screen.getByTestId("quick-chat-session-option-session-agent"));
scrollHeightValue = 1700; scrollHeightValue = 1700;
await waitFor(() => { await waitFor(() => {