feat(FN-3152): add Quick Chat FAB with slash-triggered menu, resizable chat

Merged branch consolidates the FN-3119 Quick Chat FAB with slash-triggered skill menu, the FN-3152 resizable chat sidebar, planning comment inputs for mission and milestone interview modals (FN-3139), and removes the legacy agent tree view (useAgentHierarchy hook and AgentsView tree styling). Also i

Fusion-Task-Id: FN-3152
This commit is contained in:
Fusion
2026-05-02 00:45:53 -07:00
committed by gsxdsm
parent 8dadbd6dc2
commit 9ad83d021d
3 changed files with 236 additions and 3 deletions

View File

@@ -2249,6 +2249,105 @@ describe("ChatView sidebar structure", () => {
});
});
describe("resizable sidebar", () => {
beforeEach(() => {
localStorage.clear();
});
it("renders desktop resize handle with separator ARIA attributes", () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const handle = screen.getByRole("separator", { name: "Resize chat sidebar" });
expect(handle).toHaveAttribute("aria-orientation", "vertical");
expect(handle).toHaveAttribute("aria-valuemin", "180");
expect(handle).toHaveAttribute("aria-valuemax", "500");
expect(handle).toHaveAttribute("aria-valuenow", "280");
expect(handle).toHaveAttribute("tabindex", "0");
viewportSpy.mockRestore();
});
it("updates sidebar width while dragging", () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const handle = screen.getByRole("separator", { name: "Resize chat sidebar" });
fireEvent.pointerDown(handle, { pointerId: 1, clientX: 280 });
fireEvent.pointerMove(document, { pointerId: 1, clientX: 360 });
const sidebar = document.querySelector(".chat-sidebar") as HTMLElement;
expect(sidebar.style.width).toBe("360px");
expect(handle).toHaveAttribute("aria-valuenow", "360");
viewportSpy.mockRestore();
});
it("clamps width between min and max", () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const handle = screen.getByRole("separator", { name: "Resize chat sidebar" });
fireEvent.pointerDown(handle, { pointerId: 1, clientX: 280 });
fireEvent.pointerMove(document, { pointerId: 1, clientX: -1000 });
expect((document.querySelector(".chat-sidebar") as HTMLElement).style.width).toBe("180px");
fireEvent.pointerDown(handle, { pointerId: 1, clientX: 280 });
fireEvent.pointerMove(document, { pointerId: 1, clientX: 2000 });
expect((document.querySelector(".chat-sidebar") as HTMLElement).style.width).toBe("500px");
viewportSpy.mockRestore();
});
it("persists width to localStorage on pointer up", () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const handle = screen.getByRole("separator", { name: "Resize chat sidebar" });
act(() => {
fireEvent.pointerDown(handle, { pointerId: 1, clientX: 280 });
fireEvent.pointerMove(document, { pointerId: 1, clientX: 360 });
fireEvent.pointerUp(document, { pointerId: 1, clientX: 360 });
});
expect(localStorage.getItem("fusion:chat-sidebar-width")).toBe("360");
viewportSpy.mockRestore();
});
it("restores persisted width on mount", () => {
const viewportSpy = mockViewportMode("desktop");
localStorage.setItem("fusion:chat-sidebar-width", "350");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
expect((document.querySelector(".chat-sidebar") as HTMLElement).style.width).toBe("350px");
viewportSpy.mockRestore();
});
it("does not render resize handle on mobile", () => {
const viewportSpy = mockViewportMode("mobile");
setupMockChat({ sessions: [], filteredSessions: [] });
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull();
viewportSpy.mockRestore();
});
});
describe("ChatView mobile behavior", () => {
let savedVisualViewport: typeof window.visualViewport;
let savedInnerHeight: number;