FN-6530: Show only title column by default

List view now starts with a title-only table while preserving column toggle customization.

- Set the first-run default visible column set to Title only.
- Keep all optional column toggles available and saved preferences authoritative.
- Update ListView tests for title-only defaults, bulk-edit spans, and title hiding flows.

Files changed:
 packages/dashboard/app/components/ListView.tsx     |  6 +-
 .../app/components/__tests__/ListView.test.tsx     | 87 ++++++++++++++--------
 2 files changed, 61 insertions(+), 32 deletions(-)

Fusion-Task-Id: FN-6530

Fusion-Task-Lineage: 7838b5fa-2424-4fb6-9e80-7806aaf6eeb3
This commit is contained in:
gsxdsm
2026-06-17 01:39:31 -07:00
parent d0de886f11
commit d7a9dfe398
2 changed files with 61 additions and 32 deletions

View File

@@ -49,7 +49,11 @@ type SortDirection = "asc" | "desc";
// Column visibility types
const ALL_LIST_COLUMNS = ["title", "status", "column", "retries", "dependencies", "progress"] as const;
const DEFAULT_LIST_COLUMNS = ["title", "status", "column", "retries"] as const;
/*
FNXC:ListView 2026-06-17-01:10:
First-run list view users should see only the Title column by default for a cleaner table. Other columns remain opt-in through the Columns view-options dropdown, and any saved kb-dashboard-list-columns preference continues to override this default.
*/
const DEFAULT_LIST_COLUMNS = ["title"] as const;
type ListColumn = typeof ALL_LIST_COLUMNS[number];
function getNodeStatusLabel(status: NodeInfo["status"], t: TFunction<"app">): string {

View File

@@ -333,6 +333,7 @@ describe("ListView", () => {
subscribeSseMock.mockClear();
for (const key of Object.keys(listViewSseHandlers)) delete listViewSseHandlers[key];
localStorage.clear();
showAllColumnsByDefault();
ensureMatchMedia();
vi.spyOn(window, "matchMedia").mockImplementation((query: string) => ({
matches: false,
@@ -1526,6 +1527,7 @@ describe("ListView", () => {
createMockTask({ id: "FN-002", column: "todo" }),
];
localStorage.clear();
renderListView({ tasks });
enterBulkEditMode();
@@ -1533,18 +1535,18 @@ describe("ListView", () => {
const sectionHeaders = screen.getAllByRole("row").filter(r => r.className.includes("list-section-header"));
// Verify each section header has colSpan that includes the checkbox column
// Default visible columns: title, status, column, retries (4 columns)
// Plus checkbox column = 5 total
// Default visible columns: title (1 column)
// Plus checkbox column = 2 total
for (const header of sectionHeaders) {
const th = header.querySelector("th.list-section-cell");
expect(th).not.toBeNull();
expect(th!.getAttribute("colSpan")).toBe("5"); // visibleColumns.size (4) + 1 for checkbox
expect(th!.getAttribute("colSpan")).toBe("2"); // visibleColumns.size (1) + 1 for checkbox
}
// Also verify empty section cells span full width
const emptyCells = screen.getAllByRole("cell").filter(c => c.className.includes("list-empty-cell"));
for (const cell of emptyCells) {
expect(cell.getAttribute("colSpan")).toBe("5");
expect(cell.getAttribute("colSpan")).toBe("2");
}
});
@@ -1811,6 +1813,39 @@ describe("ListView Column Visibility", () => {
expect(screen.getByLabelText("Title")).toBeDefined();
expect(screen.getByLabelText("Status")).toBeDefined();
expect(screen.getByLabelText("Column")).toBeDefined();
expect(screen.getByLabelText("Retries")).toBeDefined();
expect(screen.getByLabelText("Dependencies")).toBeDefined();
expect(screen.getByLabelText("Progress")).toBeDefined();
});
it("shows only the Title column by default while keeping all column toggles available", () => {
const tasks = [
createMockTask({
id: "FN-001",
title: "Title-only default task",
column: "triage",
status: "pending",
}),
];
renderListView({ tasks });
const table = document.querySelector(".list-table");
expect(table).not.toBeNull();
const tableHeader = table?.querySelector("thead");
expect(tableHeader).not.toBeNull();
expect(within(tableHeader as HTMLElement).getByRole("columnheader", { name: /title/i })).toBeDefined();
expect(within(tableHeader as HTMLElement).queryByRole("columnheader", { name: /status/i })).toBeNull();
expect(within(tableHeader as HTMLElement).queryByRole("columnheader", { name: /column/i })).toBeNull();
expect(within(tableHeader as HTMLElement).queryByRole("columnheader", { name: /retries/i })).toBeNull();
expect(within(tableHeader as HTMLElement).queryByRole("columnheader", { name: /dependencies/i })).toBeNull();
expect(within(tableHeader as HTMLElement).queryByRole("columnheader", { name: /progress/i })).toBeNull();
expect(within(table as HTMLElement).getByText("Title-only default task")).toBeDefined();
expect(screen.getByLabelText("Title")).toBeDefined();
expect(screen.getByLabelText("Status")).toBeDefined();
expect(screen.getByLabelText("Column")).toBeDefined();
expect(screen.getByLabelText("Retries")).toBeDefined();
expect(screen.getByLabelText("Dependencies")).toBeDefined();
expect(screen.getByLabelText("Progress")).toBeDefined();
});
@@ -1818,13 +1853,10 @@ describe("ListView Column Visibility", () => {
const tasks = [createMockTask({ id: "FN-001", title: "Test Task" })];
renderListView({ tasks });
// Uncheck the Title column
const checkboxes = screen.getAllByRole("checkbox");
const titleCheckbox = checkboxes.find(
cb => cb.parentElement?.textContent?.includes("Title")
);
expect(titleCheckbox).toBeDefined();
fireEvent.click(titleCheckbox!);
// Enable a second column first so the last-visible-column guard allows hiding Title.
fireEvent.click(screen.getByLabelText("Status"));
const titleCheckbox = screen.getByLabelText("Title");
fireEvent.click(titleCheckbox);
// Title column should no longer be visible in the table
const table = document.querySelector(".list-table");
@@ -1835,24 +1867,18 @@ describe("ListView Column Visibility", () => {
const tasks = [createMockTask({ id: "FN-001", title: "Test Task" })];
renderListView({ tasks });
// Find and uncheck the Title column
const checkboxes = screen.getAllByRole("checkbox");
const titleCheckbox = checkboxes.find(
cb => cb.parentElement?.textContent?.includes("Title")
);
expect(titleCheckbox).toBeDefined();
fireEvent.click(titleCheckbox!);
// Enable a second column first so the last-visible-column guard allows hiding Title.
fireEvent.click(screen.getByLabelText("Status"));
const titleCheckbox = screen.getByLabelText("Title");
fireEvent.click(titleCheckbox);
// Verify Title is hidden
const table = document.querySelector(".list-table");
expect(table?.textContent).not.toContain("Test Task");
// Re-check the Title column (still in the same dropdown session)
const titleCheckbox2 = screen.getAllByRole("checkbox").find(
cb => cb.parentElement?.textContent?.includes("Title")
);
expect(titleCheckbox2).toBeDefined();
fireEvent.click(titleCheckbox2!);
const titleCheckbox2 = screen.getByLabelText("Title");
fireEvent.click(titleCheckbox2);
// Title column should be visible again
const tableAfter = document.querySelector(".list-table");
@@ -1863,7 +1889,8 @@ describe("ListView Column Visibility", () => {
const tasks = [createMockTask({ id: "FN-001", title: "Test Task" })];
renderListView({ tasks });
// Uncheck Title
// Enable a second column first, then uncheck Title.
fireEvent.click(screen.getByLabelText("Status"));
const titleCheckbox = screen.getByLabelText("Title");
fireEvent.click(titleCheckbox);
@@ -1871,6 +1898,7 @@ describe("ListView Column Visibility", () => {
const saved = localStorage.getItem(scopedStorageKey("kb-dashboard-list-columns"));
expect(saved).toBeTruthy();
const parsed = JSON.parse(saved!);
expect(parsed).toContain("status");
expect(parsed).not.toContain("title");
});
@@ -1936,20 +1964,17 @@ describe("ListView Column Visibility", () => {
expect(rows[2].textContent).toContain("FN-003");
});
it("shows reduced default columns when no localStorage", () => {
it("shows only title by default when no localStorage", () => {
const tasks = [
createMockTask({ id: "FN-001", title: "Test Task", status: "pending", column: "triage" }),
];
renderListView({ tasks });
// Reduced default columns should be visible
// The title column should be the only visible first-run column.
expect(screen.getByText("FN-001")).toBeDefined();
expect(screen.getByText("Test Task")).toBeDefined();
expect(screen.getByText("pending")).toBeDefined();
const columnBadge = document.querySelector(".list-column-badge");
expect(columnBadge?.textContent).toContain("Planning");
// Optional columns should be hidden by default
expect(screen.queryByText("pending")).toBeNull();
expect(document.querySelector(".list-column-badge")).toBeNull();
expect(document.querySelector(".list-cell-deps")).toBeNull();
expect(document.querySelector(".list-cell-progress")).toBeNull();
});