FN-6039: expand triage task detail descriptions by default

Open triage task details with long title or description text expanded by default.

- initialize and reset the task detail description expansion state from the task column
- keep manual Show more and Show less toggles working for the current task
- add rendering coverage for triage defaults, fallbacks, task switches, and icon mocks

Files changed:
 packages/dashboard/app/components/TaskDetailModal.tsx              |   6 +-
 packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx | 154 +++++++++++++++++++++
 packages/dashboard/app/components/__tests__/TaskDetailModal.test-helpers.ts     |  11 ++
 3 files changed, 168 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-6039

Fusion-Task-Lineage: b3bd4911-58ae-4592-9dad-e9164081a4d9
This commit is contained in:
gsxdsm
2026-06-08 13:06:45 -07:00
parent 9c84ba2e9f
commit 5c6c9af8dc
3 changed files with 168 additions and 3 deletions

View File

@@ -659,12 +659,12 @@ export function TaskDetailContent({
// Reset description expanded state when task changes
useEffect(() => {
setDescriptionExpanded(false);
}, [task.id]);
setDescriptionExpanded(task.column === "triage");
}, [task.column, task.id]);
const [logSubview, setLogSubview] = useState<"activity" | "agent-log">("activity");
const [highlightStallCode, setHighlightStallCode] = useState<string | null>(null);
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
const [descriptionExpanded, setDescriptionExpanded] = useState(() => task.column === "triage");
const [attachments, setAttachments] = useState<TaskAttachment[]>(task.attachments || []);
const [uploading, setUploading] = useState(false);
const [dependencies, setDependencies] = useState<string[]>(task.dependencies || []);

View File

@@ -1300,6 +1300,54 @@ describe("TaskDetailModal", () => {
});
describe("description truncation", () => {
it("expands long triage title by default with Show less button", () => {
const longTitle = "Triage title ".repeat(25);
const { container } = render(
<TaskDetailModal
task={makeTask({
column: "triage",
title: longTitle,
description: "Triage planning context",
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const h2 = container.querySelector("h2.detail-title");
expect(h2?.textContent).toBe(longTitle);
const toggle = container.querySelector(".detail-description-toggle");
expect(toggle?.textContent).toBe("Show less");
});
it("expands long triage description by default when title is missing", () => {
const longDescription = "Triage description ".repeat(20);
const { container } = render(
<TaskDetailModal
task={makeTask({
column: "triage",
title: undefined,
description: longDescription,
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const h2 = container.querySelector("h2.detail-title");
expect(h2?.textContent).toBe(longDescription);
const toggle = container.querySelector(".detail-description-toggle");
expect(toggle?.textContent).toBe("Show less");
});
it("truncates description over 200 characters with Show more button", () => {
const longDescription = "A".repeat(250);
const { container } = render(
@@ -1350,6 +1398,43 @@ describe("TaskDetailModal", () => {
expect(toggle.textContent).toBe("Show less");
});
it("lets Show less and Show more override the triage default for the current task", async () => {
const longDescription = "C".repeat(250);
const { container } = render(
<TaskDetailModal
task={makeTask({
column: "triage",
title: undefined,
description: longDescription,
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const toggle = container.querySelector(".detail-description-toggle") as HTMLButtonElement;
expect(container.querySelector("h2.detail-title")?.textContent).toBe("C".repeat(250));
expect(toggle.textContent).toBe("Show less");
await act(async () => {
fireEvent.click(toggle);
});
expect(container.querySelector("h2.detail-title")?.textContent).toBe("C".repeat(200) + "…");
expect(toggle.textContent).toBe("Show more");
await act(async () => {
fireEvent.click(toggle);
});
expect(container.querySelector("h2.detail-title")?.textContent).toBe("C".repeat(250));
expect(toggle.textContent).toBe("Show less");
});
it("collapses description when Show less is clicked", async () => {
const longDescription = "C".repeat(250);
const { container } = render(
@@ -1383,6 +1468,29 @@ describe("TaskDetailModal", () => {
expect(toggle.textContent).toBe("Show more");
});
it("does not show toggle for empty title and description fallback to task id", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({
id: "FN-EMPTY",
column: "triage",
title: undefined,
description: undefined,
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const h2 = container.querySelector("h2.detail-title");
expect(h2?.textContent).toBe("FN-EMPTY");
expect(container.querySelector(".detail-description-toggle")).toBeNull();
});
it("does not show toggle for description under 200 characters", () => {
const shortDescription = "Short description";
const { container } = render(
@@ -1449,6 +1557,52 @@ describe("TaskDetailModal", () => {
expect(toggle?.textContent).toBe("Show more");
});
it("resets to expanded when switching from a non-triage task to a triage task", async () => {
const todoDescription = "G".repeat(250);
const triageDescription = "H".repeat(250);
const { container, rerender } = render(
<TaskDetailModal
task={makeTask({
id: "FN-TODO",
column: "todo",
title: undefined,
description: todoDescription,
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(container.querySelector("h2.detail-title")?.textContent).toBe("G".repeat(200) + "…");
expect(container.querySelector(".detail-description-toggle")?.textContent).toBe("Show more");
rerender(
<TaskDetailModal
task={makeTask({
id: "FN-TRIAGE",
column: "triage",
title: undefined,
description: triageDescription,
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
await waitFor(() => {
expect(container.querySelector("h2.detail-title")?.textContent).toBe("H".repeat(250));
});
expect(container.querySelector(".detail-description-toggle")?.textContent).toBe("Show less");
});
it("resets expanded state when task changes", async () => {
const longDescription1 = "E".repeat(250);
const longDescription2 = "F".repeat(250);

View File

@@ -69,6 +69,17 @@ vi.mock("lucide-react", () => ({
GitMerge: () => null,
GitBranch: () => null,
AlertTriangle: () => null,
Play: () => null,
Flag: () => null,
Terminal: () => null,
Shield: () => null,
PauseCircle: () => null,
Split: () => null,
Merge: () => null,
Repeat: () => null,
ClipboardCheck: () => null,
ListChecks: () => null,
Code2: () => null,
}));
vi.mock("../../hooks/useAgentLogs", () => ({