feat(FN-2271): optimize chat sidebar layout for mobile and desktop

- Add explicit sidebar section class names in ChatView for header, search, list, and footer layout control
- Add a mobile-only footer New Chat action while keeping the desktop header button to avoid duplicate desktop CTAs
- Update mobile chat sidebar CSS to use full-height layout with hidden header/search and a scrollable session list
- Expand ChatView tests to validate sidebar structure, mobile New Chat behavior, and mobile CSS contract rules
This commit is contained in:
Fusion
2026-04-23 04:28:49 -07:00
committed by gsxdsm
parent 585e48041d
commit a332c8f7dc
6 changed files with 618 additions and 18 deletions

View File

@@ -55,6 +55,8 @@ import {
fetchPiSettings,
updatePiSettings,
installPiPackage,
fetchPiExtensions,
updatePiExtensions,
fetchProjectTasks,
fetchProjectConfig,
fetchExecutorStats,
@@ -5933,3 +5935,115 @@ describe("installPiPackage", () => {
}));
});
});
describe("fetchPiExtensions", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("sends GET to /api/settings/pi-extensions", async () => {
const mockSettings = {
extensions: [
{ id: "ext-1", name: "Example", source: "fusion-global", path: "/path/to/ext", enabled: true },
],
disabledIds: [],
settingsPath: "/path/to/settings",
};
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, mockSettings));
const result = await fetchPiExtensions();
expect(result.extensions).toEqual(mockSettings.extensions);
expect(result.disabledIds).toEqual(mockSettings.disabledIds);
expect(globalThis.fetch).toHaveBeenCalledWith("/api/settings/pi-extensions", expect.any(Object));
});
it("includes projectId query param when provided", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, { extensions: [], disabledIds: [], settingsPath: "" }));
await fetchPiExtensions("proj-123");
expect(globalThis.fetch).toHaveBeenCalledWith("/api/settings/pi-extensions?projectId=proj-123", expect.any(Object));
});
it("returns empty extensions when no extensions", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, { extensions: [], disabledIds: [], settingsPath: "" }));
const result = await fetchPiExtensions();
expect(result.extensions).toEqual([]);
});
it("handles all source types", async () => {
const mockSettings = {
extensions: [
{ id: "ext-1", name: "Fusion Global", source: "fusion-global", path: "/path", enabled: true },
{ id: "ext-2", name: "Pi Global", source: "pi-global", path: "/path", enabled: true },
{ id: "ext-3", name: "Fusion Project", source: "fusion-project", path: "/path", enabled: false },
{ id: "ext-4", name: "Pi Project", source: "pi-project", path: "/path", enabled: true },
],
disabledIds: ["ext-3"],
settingsPath: "/path/to/settings",
};
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, mockSettings));
const result = await fetchPiExtensions();
expect(result.extensions).toHaveLength(4);
expect(result.extensions[0].source).toBe("fusion-global");
});
it("throws error on API failure", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(false, { error: "Server error" }, 500));
await expect(fetchPiExtensions()).rejects.toThrow();
});
});
describe("updatePiExtensions", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("sends PUT to /api/settings/pi-extensions with disabledIds", async () => {
const mockSettings = { extensions: [], disabledIds: ["ext-1", "ext-2"], settingsPath: "" };
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, mockSettings));
const result = await updatePiExtensions(["ext-1", "ext-2"]);
expect(globalThis.fetch).toHaveBeenCalledWith("/api/settings/pi-extensions", expect.objectContaining({
method: "PUT",
body: JSON.stringify({ disabledIds: ["ext-1", "ext-2"] }),
}));
expect(result.disabledIds).toEqual(["ext-1", "ext-2"]);
});
it("includes projectId query param when provided", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, { extensions: [], disabledIds: [], settingsPath: "" }));
await updatePiExtensions([], "proj-456");
expect(globalThis.fetch).toHaveBeenCalledWith("/api/settings/pi-extensions?projectId=proj-456", expect.any(Object));
});
it("sends empty disabledIds array", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, { extensions: [], disabledIds: [], settingsPath: "" }));
await updatePiExtensions([]);
expect(globalThis.fetch).toHaveBeenCalledWith("/api/settings/pi-extensions", expect.objectContaining({
method: "PUT",
body: JSON.stringify({ disabledIds: [] }),
}));
});
it("throws error on API failure", async () => {
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(false, { error: "Server error" }, 500));
await expect(updatePiExtensions(["ext-1"])).rejects.toThrow();
});
});