diff --git a/packages/dashboard/app/components/ListView.css b/packages/dashboard/app/components/ListView.css index 1029795b02..5e50cb4038 100644 --- a/packages/dashboard/app/components/ListView.css +++ b/packages/dashboard/app/components/ListView.css @@ -581,10 +581,18 @@ FN-6529 requires list-view agent-active tasks to use a simple static highlight i max-width: 100%; } +/* FNXC:ListView 2026-06-21-01:42: Desktop list task titles clamp to two wrapped lines so longer task names remain legible in the narrower split sidebar without allowing unbounded row growth. */ .list-title-text { + display: -webkit-box; + max-width: 100%; + min-width: 0; overflow: hidden; + overflow-wrap: anywhere; text-overflow: ellipsis; - white-space: nowrap; + white-space: normal; + word-break: break-word; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; } .list-cell-date { diff --git a/packages/dashboard/app/components/ListView.tsx b/packages/dashboard/app/components/ListView.tsx index 56adba3ebd..a1a3c9c857 100644 --- a/packages/dashboard/app/components/ListView.tsx +++ b/packages/dashboard/app/components/ListView.tsx @@ -179,7 +179,7 @@ function readSidebarWidth(projectId?: string): number { return fallbackWidth; } -const LIST_SIDEBAR_MIN_WIDTH = 280; +const LIST_SIDEBAR_MIN_WIDTH = 200; // FNXC:ListView 2026-06-21-01:42: The desktop task-list split sidebar minimum is 200 instead of 280 so users can shrink the left panel further on narrow desktop layouts while resize, keyboard, and ARIA paths share one clamp value. const LIST_SIDEBAR_MAX_RATIO = 0.65; const LIST_SIDEBAR_KEYBOARD_STEP = 16; diff --git a/packages/dashboard/app/components/__tests__/ListView.test.tsx b/packages/dashboard/app/components/__tests__/ListView.test.tsx index a3b0614a5a..18df7d3e3b 100644 --- a/packages/dashboard/app/components/__tests__/ListView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ListView.test.tsx @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { describe, it, expect, vi } from "vitest"; import { useState } from "react"; import { render, screen, fireEvent, waitFor, within, act } from "@testing-library/react"; @@ -400,7 +401,7 @@ describe("ListView", () => { expect(document.getElementById("list-view-options-panel")).toBeNull(); }); - it("displays tasks in table format", () => { + it("displays tasks in table format with two-line title styling", () => { const tasks = [ createMockTask({ id: "FN-001", title: "First Task" }), createMockTask({ id: "FN-002", title: "Second Task" }), @@ -412,6 +413,13 @@ describe("ListView", () => { expect(screen.getByText("First Task")).toBeDefined(); expect(screen.getByText("FN-002")).toBeDefined(); expect(screen.getByText("Second Task")).toBeDefined(); + const listTitleTextRule = readFileSync("app/components/ListView.css", "utf8").match(/\.list-title-text\s*\{[^}]*\}/)?.[0] ?? ""; + expect(listTitleTextRule).toContain("display: -webkit-box"); + expect(listTitleTextRule).toContain("-webkit-line-clamp: 2"); + expect(listTitleTextRule).toContain("-webkit-box-orient: vertical"); + expect(listTitleTextRule).toContain("white-space: normal"); + expect(listTitleTextRule).toContain("overflow-wrap: anywhere"); + expect(listTitleTextRule).not.toContain("white-space: nowrap"); }); it("shows fast indicator in desktop rows only for fast-mode tasks", () => { @@ -1072,23 +1080,27 @@ describe("ListView", () => { viewportSpy.mockRestore(); }); - it("supports keyboard resizing on the desktop split-pane handle", () => { + it("supports keyboard resizing on the desktop split-pane handle", async () => { const viewportSpy = mockDesktopViewport(); const clientWidthSpy = vi.spyOn(window.HTMLElement.prototype, "clientWidth", "get").mockReturnValue(1000); + localStorage.setItem(scopedStorageKey("kb-dashboard-list-sidebar-width"), "150"); const tasks = [createMockTask({ id: "FN-001", title: "Task" })]; renderListView({ tasks }); + await waitFor(() => expect(screen.getByTestId("list-split-sidebar")).toHaveStyle({ width: "200px" })); const handle = screen.getByTestId("list-split-resize-handle"); const startWidth = Number(handle.getAttribute("aria-valuenow")); expect(handle).toHaveAttribute("tabindex", "0"); - expect(handle).toHaveAttribute("aria-valuemin", "280"); - expect(Number(handle.getAttribute("aria-valuemax"))).toBeGreaterThanOrEqual(280); + expect(handle).toHaveAttribute("aria-valuemin", "200"); + expect(Number(handle.getAttribute("aria-valuemax"))).toBeGreaterThanOrEqual(200); fireEvent.keyDown(handle, { key: "ArrowRight" }); - expect(Number(handle.getAttribute("aria-valuenow"))).toBeGreaterThan(startWidth); + fireEvent.keyDown(handle, { key: "Home" }); + expect(handle).toHaveAttribute("aria-valuenow", "200"); + expect(screen.getByTestId("list-split-sidebar")).toHaveStyle({ width: "200px" }); clientWidthSpy.mockRestore(); viewportSpy.mockRestore(); });