From 595d323ce631f1b76ca06fbef4275a6888e27f9a Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 10 Jul 2026 23:11:46 -0700 Subject: [PATCH] FN-7811: convert Task Documents tab to left-sidebar list + right-pane viewer Reworks the Artifacts view's Task Documents tab to reuse the Project Files left-sidebar/right-pane layout instead of expandable task-grouped cards, giving both tabs a consistent browsing pattern. - Replace the collapsible TaskGroup/DocumentCard components with a documents-project-layout sidebar listing documents grouped by task (with revision metadata and task status badges) and a right pane rendering the selected document's content - Add separate selectedTaskDocumentId selection state so tab switching never leaks Project Files content into Task Documents (and vice versa), with desktop/mobile gating matching the Project Files pattern - Preserve the existing Plain/Markdown render toggle for task document content; select-to-comment stays Project-Files-only for this change (tracked as follow-up) - Update DocumentsView.css for the new sidebar/right-pane structure and rewrite DocumentsView.test.tsx coverage for the new interaction model - Update docs/dashboard-guide.md to describe the shared sidebar/right-pane browsing pattern for Task Documents - Add changeset fn-7811-task-documents-sidebar.md (minor) Files changed: .changeset/fn-7811-task-documents-sidebar.md | 7 + docs/dashboard-guide.md | 4 +- .../dashboard/app/components/DocumentsView.css | 181 ++++-------- .../dashboard/app/components/DocumentsView.tsx | 308 ++++++++++----------- .../components/__tests__/DocumentsView.test.tsx | 116 ++++++-- 5 files changed, 299 insertions(+), 317 deletions(-) Fusion-Task-Id: FN-7811 Fusion-Task-Lineage: e943ebf8-6e8e-4f5f-a55c-99c949be2624 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7811-task-documents-sidebar.md | 7 + docs/dashboard-guide.md | 4 +- .../app/components/DocumentsView.css | 187 +++-------- .../app/components/DocumentsView.tsx | 308 +++++++++--------- .../__tests__/DocumentsView.test.tsx | 116 +++++-- 5 files changed, 302 insertions(+), 320 deletions(-) create mode 100644 .changeset/fn-7811-task-documents-sidebar.md diff --git a/.changeset/fn-7811-task-documents-sidebar.md b/.changeset/fn-7811-task-documents-sidebar.md new file mode 100644 index 0000000000..e833d8be68 --- /dev/null +++ b/.changeset/fn-7811-task-documents-sidebar.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Artifacts view — Task Documents now uses a left-sidebar list with a right-pane content viewer. +category: feature +dev: DocumentsView Task Documents tab reuses the Project Files `documents-project-layout` sidebar/right-pane pattern with a separate selection state and desktop/mobile gating; the markdown/plain toggle is preserved. Select-to-comment stays Project-Files-only (tracked as a follow-up). diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 3abc71f55f..6c3627356b 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -817,9 +817,9 @@ Artifacts view aggregates registered artifacts, project markdown files, and task Features: -- Group task documents by task ID (with revision history metadata) and show the parent task status badge in each task group header when status metadata is available +- Browse **Task Documents** in the same left-sidebar/right-pane pattern as **Project Files**: the sidebar groups document entries by task ID with revision metadata and parent task status badges when available, while the right pane loads the selected document content - Search documents across tasks -- Open project markdown files with inline preview +- Open project markdown files and task documents with inline preview - Browse the **Artifacts** tab for registry media registered by any agent, dashboard chat/user action, or system tool across tasks - Already-open global and task-detail artifact lists refresh live from the artifact registry event when an agent, dashboard chat session, user action, or system tool registers a new artifact, while preserving active search filters and task scoping - Use the tab-count badges to see the current counts for Project Files, Task Documents, and Artifacts; the Artifacts badge reflects the loaded `GET /api/artifacts` result set, including active search filters diff --git a/packages/dashboard/app/components/DocumentsView.css b/packages/dashboard/app/components/DocumentsView.css index 7dcef04d48..ff30ed7d1e 100644 --- a/packages/dashboard/app/components/DocumentsView.css +++ b/packages/dashboard/app/components/DocumentsView.css @@ -357,65 +357,70 @@ Subtle view-only indicator for the project-file preview header; muted so it info color: var(--color-error); } -.documents-task-list-wrap { - width: 100%; -} - -.documents-view-list { +/* +FNXC:DocumentsView 2026-07-10-17:40: +FN-7811 reuses the Project Files shell for Task Documents, while task-scoped sidebar groups preserve task id/title/status context before each selectable document entry. Keep these styles scoped so Project Files continues to own the generic markdown-file list visuals. +*/ +.documents-task-documents-sidebar { display: flex; flex-direction: column; - gap: var(--space-md); } -/* Document Group */ -.documents-group { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - overflow: hidden; - background: var(--card); +.documents-task-sidebar-group { + border-bottom: 1px solid var(--border); } -.documents-group-header { - display: grid; - grid-template-columns: minmax(0, 1fr) auto auto auto; - align-items: center; +.documents-task-sidebar-group:last-child { + border-bottom: none; +} + +.documents-task-sidebar-group-header { + display: flex; + flex-direction: column; gap: var(--space-sm); - padding: var(--space-sm) var(--space-md); + padding: var(--space-md); background: var(--surface); border-bottom: 1px solid var(--border); } -.documents-group-toggle-btn { +.documents-task-sidebar-title-wrap { + display: flex; + flex-direction: column; + gap: var(--space-xs); + min-width: 0; +} + +.documents-task-sidebar-title { display: flex; align-items: center; gap: var(--space-sm); + margin: 0; min-width: 0; - border: none; - background: none; - color: var(--text); - text-align: left; - padding: var(--space-xs) var(--space-sm); - border-radius: var(--radius-md); - cursor: pointer; - transition: background var(--transition-fast); - min-height: 36px; } -.documents-group-toggle-btn:hover { - background: var(--card-hover); -} - -.documents-group-toggle-btn:focus-visible { - outline: none; - box-shadow: var(--focus-ring-strong); -} - -.documents-group-toggle { +.documents-task-sidebar-actions { display: flex; align-items: center; - justify-content: center; - color: var(--text-muted); - flex-shrink: 0; + justify-content: space-between; + gap: var(--space-sm); +} + +.documents-task-document-item { + border-left-color: transparent; +} + +.documents-task-document-title-block { + display: flex; + flex: 1; + min-width: 0; + flex-direction: column; + gap: var(--space-xs); +} + +.documents-task-document-meta { + padding: 0; + border-bottom: none; + flex-wrap: wrap; } .documents-group-task-id { @@ -470,13 +475,6 @@ Subtle view-only indicator for the project-file preview header; muted so it info box-shadow: var(--focus-ring-strong); } -.documents-group-content { - padding: var(--space-md); - display: flex; - flex-direction: column; - gap: var(--space-sm); -} - /* Document Card */ .document-card { background: var(--card); @@ -499,48 +497,6 @@ Subtle view-only indicator for the project-file preview header; muted so it info border-bottom: 1px solid var(--border); } -.document-card-key { - display: flex; - align-items: center; - gap: var(--space-xs); - color: var(--text); -} - -.document-card-key svg { - color: var(--text-muted); - flex-shrink: 0; -} - -.document-card-key-text { - font-family: var(--font-mono); - font-size: 13px; - font-weight: 500; -} - -.document-card-revision-badge { - font-size: calc(var(--space-sm) + var(--space-xs) * 0.75); - color: var(--text-muted); - background: var(--surface); - padding: var(--space-xs) var(--space-sm); - border-radius: var(--radius-pill); - border: 1px solid var(--border); -} - -.document-card-expand-btn { - padding: var(--space-xs); - color: var(--text-muted); -} - -.document-card-expand-btn:hover { - color: var(--text); -} - -.document-card-actions { - display: flex; - align-items: center; - gap: var(--space-xs); -} - .document-mode-toggle { padding: var(--space-xs) var(--space-sm); font-size: 11px; @@ -570,16 +526,6 @@ Subtle view-only indicator for the project-file preview header; muted so it info border-color: var(--accent); } -.document-card-content-markdown { - /* Container chrome only; markdown styles delegated to .markdown-body */ -} - -.document-card-content-header { - display: flex; - justify-content: flex-end; - margin-bottom: var(--space-sm); -} - .document-card-meta { display: flex; align-items: center; @@ -598,25 +544,6 @@ Subtle view-only indicator for the project-file preview header; muted so it info padding: var(--space-md); } -.document-card-preview { - margin: 0; - font-size: 13px; - color: var(--text-muted); - line-height: 1.5; - word-break: break-word; -} - -.document-card-preview-truncated { - margin: 0; - font-size: 13px; - color: var(--text-dim); -} - -.document-card-content--expanded .document-card-preview, -.document-card-content--expanded .document-card-preview-truncated { - display: none; -} - .document-card-content-text { margin: 0; font-size: 13px; @@ -932,42 +859,32 @@ The artifacts tab is a thumbnail-first responsive media gallery for agent-create min-height: 160px; } - .documents-group-header { - grid-template-columns: minmax(0, 1fr) auto; - grid-template-areas: - "toggle count" - "status status" - "link link"; - row-gap: var(--space-xs); - } - - .documents-group-toggle-btn { - grid-area: toggle; + .documents-task-sidebar-actions { + align-items: stretch; + flex-direction: column; } .documents-group-status { - grid-area: status; justify-self: start; } .documents-group-count { - grid-area: count; justify-self: end; } .documents-group-task-link { - grid-area: link; width: 100%; justify-self: stretch; min-height: 44px; } .documents-group-task-title { - max-width: 180px; + max-width: 11rem; } - .document-card-key-text { - font-size: 12px; + .documents-task-document-header { + align-items: stretch; + flex-direction: column; } .documents-artifact-gallery, diff --git a/packages/dashboard/app/components/DocumentsView.tsx b/packages/dashboard/app/components/DocumentsView.tsx index 3fc8b88337..f352f03bab 100644 --- a/packages/dashboard/app/components/DocumentsView.tsx +++ b/packages/dashboard/app/components/DocumentsView.tsx @@ -1,7 +1,7 @@ import "./DocumentsView.css"; import { useState, useMemo, useCallback, useEffect, useRef, type ChangeEvent } from "react"; import { useTranslation } from "react-i18next"; -import { ArrowLeft, FileText, ChevronDown, ChevronUp, ChevronRight, RefreshCw, Search, X, Eye, EyeOff } from "lucide-react"; +import { ArrowLeft, FileText, RefreshCw, Search, X, Eye, EyeOff } from "lucide-react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { ColumnId, TaskDocumentWithTask, TaskDetail } from "@fusion/core"; @@ -29,22 +29,6 @@ export interface DocumentsViewProps { onSendSelectionToTask?: (description: string) => void; } -interface DocumentCardProps { - document: TaskDocumentWithTask; - renderMarkdown: boolean; - onToggleMarkdown: () => void; -} - -interface TaskGroupProps { - taskId: string; - taskTitle?: string; - documents: TaskDocumentWithTask[]; - taskColumn?: string; - onOpenTask: (taskId: string) => void; - renderMarkdownStates: Map; - onToggleMarkdown: (docId: string) => void; -} - function formatTimestamp(iso?: string): string { if (!iso) return ""; return new Date(iso).toLocaleString(); @@ -74,137 +58,14 @@ function getTaskColumnStatusDotClass(taskColumn: string): string { return "status-dot status-dot--connecting"; } -function DocumentCard({ document, renderMarkdown, onToggleMarkdown }: DocumentCardProps) { - const { t } = useTranslation("app"); - const [expanded, setExpanded] = useState(false); - - const preview = getContentPreview(document.content); - const showExpand = document.content.length > 200; - - return ( -
-
-
- - {document.key} - v{document.revision} -
-
- -
-
- -
- {document.author} - · - {formatTimestamp(document.updatedAt)} -
- -
- {expanded ? ( - <> -
- -
- {renderMarkdown ? ( -
-
- {document.content} -
-
- ) : ( -
{document.content}
- )} - - ) : ( -

{preview}

- )} - {showExpand && !expanded && ( -

…

- )} -
-
- ); -} - -function TaskGroup({ taskId, taskTitle, documents, taskColumn, onOpenTask, renderMarkdownStates, onToggleMarkdown }: TaskGroupProps) { - const { t } = useTranslation("app"); - const columnLabel = useColumnLabel(); - const [expanded, setExpanded] = useState(false); - const taskStatusLabel = taskColumn ? columnLabel(taskColumn as ColumnId) : null; - const taskStatusDotClass = taskColumn ? getTaskColumnStatusDotClass(taskColumn) : "status-dot"; - - return ( -
-
- - - {taskStatusLabel ? ( - - - ) : null} - - {t("documents.docCount", "{{count}} doc{{plural}}", { count: documents.length, plural: documents.length !== 1 ? "s" : "" })} - - -
- - {expanded && ( -
- {documents.map((doc) => ( - onToggleMarkdown(doc.id)} - /> - ))} -
- )} -
- ); -} - export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifactTaskDetail, onSendSelectionToTask }: DocumentsViewProps) { const { t } = useTranslation("app"); // FNXC:ArtifactsView 2026-07-11-11:30: Artifacts is the first tab and the landing tab — the view is the artifact gallery first, with project files and task documents as secondary tabs. const [activeTab, setActiveTab] = useState("artifacts"); + const columnLabel = useColumnLabel(); const [searchQuery, setSearchQuery] = useState(""); const [selectedFile, setSelectedFile] = useState(null); + const [selectedTaskDocumentId, setSelectedTaskDocumentId] = useState(null); const [showHiddenProjectFiles, setShowHiddenProjectFiles] = useState(false); const [fileContent, setFileContent] = useState(null); const [fileLoading, setFileLoading] = useState(false); @@ -269,6 +130,7 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac useEffect(() => { setActiveTab("artifacts"); setSelectedFile(null); + setSelectedTaskDocumentId(null); setShowHiddenProjectFiles(false); setFileContent(null); setFileError(null); @@ -303,6 +165,25 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac .sort((a, b) => b.latestUpdated.localeCompare(a.latestUpdated)); }, [documents]); + + /* + FNXC:DocumentsView 2026-07-10-17:30: + Task Documents now mirrors the Project Files sidebar/right-pane contract: the task-document selection is deliberately separate from selectedFile so tab switching cannot leak project file content into the Task Documents pane. Select-to-comment remains Project-Files-only for FN-7811 and is tracked as follow-up scope; Task Documents preserves only its existing Plain/Markdown render toggle. + */ + const selectedTaskDocument = useMemo(() => { + if (!selectedTaskDocumentId) { + return null; + } + + return documents.find((doc) => doc.id === selectedTaskDocumentId) ?? null; + }, [documents, selectedTaskDocumentId]); + + useEffect(() => { + if (selectedTaskDocumentId && !selectedTaskDocument) { + setSelectedTaskDocumentId(null); + } + }, [selectedTaskDocument, selectedTaskDocumentId]); + const filteredProjectFiles = useMemo(() => { const normalizedQuery = searchQuery.trim().toLowerCase(); if (!normalizedQuery) { @@ -340,6 +221,9 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac const handleTabChange = useCallback((tab: DocumentsTab) => { setActiveTab(tab); + if (tab !== "tasks") { + setSelectedTaskDocumentId(null); + } }, []); const handleOpenTask = useCallback(async (taskId: string) => { @@ -404,6 +288,14 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac setFileLoading(false); }, []); + const handleSelectTaskDocument = useCallback((docId: string) => { + setSelectedTaskDocumentId(docId); + }, []); + + const handleBackToTaskDocumentList = useCallback(() => { + setSelectedTaskDocumentId(null); + }, []); + const handleToggleTaskDocMarkdown = useCallback((docId: string) => { setTaskDocMarkdownStates((prev) => { const next = new Map(prev); @@ -702,21 +594,123 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac )} ) : ( -
-
- {groupedDocuments.map(({ taskId, taskTitle, taskColumn, documents: taskDocs }) => ( - - ))} -
+ /* + FNXC:DocumentsView 2026-07-10-17:30: + FN-7811 requires Task Documents to use the same desktop two-pane and mobile list→detail→back gating as Project Files: show the grouped sidebar when desktop or no document is selected, and show the right-pane viewer when desktop or a document is selected. + */ +
+ {(!isMobile || !selectedTaskDocument) && ( + + )} + + {(!isMobile || selectedTaskDocument) && ( +
+ {isMobile && selectedTaskDocument && ( + + )} + + {!selectedTaskDocument ? ( +
+

{t("documents.selectTaskDocument", "Select a task document to view its content.")}

+
+ ) : ( +
+
+
+

{selectedTaskDocument.taskId} / {selectedTaskDocument.key}

+
+ {selectedTaskDocument.author} + · + {t("documents.revisionShort", "v{{revision}}", { revision: selectedTaskDocument.revision })} + · + {formatTimestamp(selectedTaskDocument.updatedAt)} +
+
+ +
+ {(taskDocMarkdownStates.get(selectedTaskDocument.id) ?? false) ? ( +
+
+ {selectedTaskDocument.content} +
+
+ ) : ( +
{selectedTaskDocument.content}
+ )} +
+ )} +
+ )}
)}
diff --git a/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx b/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx index 18da6448d2..6ecd6cbedd 100644 --- a/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx @@ -372,7 +372,49 @@ describe("DocumentsView", () => { expect(screen.queryByRole("button", { name: "Open README.md" })).not.toBeInTheDocument(); }); - it("shows collapsed task group status badges for done non-done archived custom and legacy documents", async () => { + it("renders task documents desktop sidebar and empty right pane", () => { + render(); + + fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); + + expect(screen.getByLabelText("Task documents")).toBeInTheDocument(); + expect(screen.getByLabelText("Task document content preview")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Open KB-001 plan" })).toBeInTheDocument(); + expect(screen.getByText("Select a task document to view its content.")).toBeInTheDocument(); + expect(screen.queryByText("Alpha document content")).not.toBeInTheDocument(); + }); + + it("selecting a task document loads content and marks the sidebar entry current", () => { + render(); + + fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); + const planEntry = screen.getByRole("button", { name: "Open KB-001 plan" }); + fireEvent.click(planEntry); + + expect(screen.getByText("Alpha document content")).toBeInTheDocument(); + expect(planEntry).toHaveAttribute("aria-current", "true"); + expect(screen.getByText("KB-001 / plan")).toBeInTheDocument(); + }); + + it("keeps project file and task document selections isolated across tab switches", async () => { + render(); + + fireEvent.click(screen.getByRole("button", { name: "Open README.md" })); + expect(await screen.findByText(/Hello docs/)).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); + + expect(screen.getByText("Select a task document to view its content.")).toBeInTheDocument(); + expect(screen.queryByText(/Hello docs/)).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Open KB-001 plan" })); + expect(screen.getByText("Alpha document content")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("tab", { name: /show project markdown files/i })); + expect(screen.getByText(/Hello docs/)).toBeInTheDocument(); + expect(screen.queryByText("Alpha document content")).not.toBeInTheDocument(); + }); + + it("renders task document sidebar status badges for done non-done archived custom and legacy documents", async () => { mockUseProjectMarkdownFiles.mockReturnValue({ files: [], loading: false, @@ -392,11 +434,11 @@ describe("DocumentsView", () => { fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); expect(screen.getByRole("tab", { name: /show task documents/i })).toHaveAttribute("aria-selected", "true"); - const doneGroup = screen.getByRole("button", { name: /expand documents for task KB-DONE/i }).closest(".documents-group"); - const todoGroup = screen.getByRole("button", { name: /expand documents for task KB-TODO/i }).closest(".documents-group"); - const archivedGroup = screen.getByRole("button", { name: /expand documents for task KB-ARCHIVED/i }).closest(".documents-group"); - const customGroup = screen.getByRole("button", { name: /expand documents for task KB-CUSTOM/i }).closest(".documents-group"); - const missingGroup = screen.getByRole("button", { name: /expand documents for task KB-MISSING/i }).closest(".documents-group"); + const doneGroup = screen.getByRole("heading", { name: /KB-DONE.*Done task/i }).closest(".documents-task-sidebar-group"); + const todoGroup = screen.getByRole("heading", { name: /KB-TODO.*Todo task/i }).closest(".documents-task-sidebar-group"); + const archivedGroup = screen.getByRole("heading", { name: /KB-ARCHIVED.*Archived task/i }).closest(".documents-task-sidebar-group"); + const customGroup = screen.getByRole("heading", { name: /KB-CUSTOM.*Custom task/i }).closest(".documents-task-sidebar-group"); + const missingGroup = screen.getByRole("heading", { name: /KB-MISSING.*Legacy task/i }).closest(".documents-task-sidebar-group"); expect(doneGroup).not.toBeNull(); expect(todoGroup).not.toBeNull(); @@ -414,7 +456,7 @@ describe("DocumentsView", () => { expect(screen.queryByText("Done document content")).not.toBeInTheDocument(); }); - it("keeps task group status badges as non-interactive header metadata on mobile", async () => { + it("keeps task document status badges as non-interactive sidebar metadata on mobile", async () => { window.innerWidth = 600; mockUseProjectMarkdownFiles.mockReturnValue({ files: [], @@ -435,11 +477,11 @@ describe("DocumentsView", () => { fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); expect(screen.getByRole("tab", { name: /show task documents/i })).toHaveAttribute("aria-selected", "true"); - const doneGroup = screen.getByRole("button", { name: /expand documents for task KB-DONE/i }).closest(".documents-group") as HTMLElement; + const doneGroup = screen.getByRole("heading", { name: /KB-DONE.*Done task/i }).closest(".documents-task-sidebar-group") as HTMLElement; const status = within(doneGroup).getByLabelText("Task status: Done"); expect(status).toHaveClass("documents-group-status"); - expect(status.closest(".documents-group-header")).toBeInTheDocument(); + expect(status.closest(".documents-task-sidebar-group-header")).toBeInTheDocument(); expect(status.closest("button")).toBeNull(); expect(within(doneGroup).getByRole("button", { name: /open task KB-DONE/i })).toBeInTheDocument(); }); @@ -792,7 +834,7 @@ describe("DocumentsView", () => { expect(onSendSelectionToTask).toHaveBeenCalledWith(expect.stringContaining("Review this rendered content.")); }); - it("search filters task documents", async () => { + it("search filters task documents and clears filtered-out selection", async () => { mockUseProjectMarkdownFiles.mockReturnValue({ files: [], loading: false, @@ -822,13 +864,18 @@ describe("DocumentsView", () => { fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); expect(screen.getByRole("tab", { name: /show task documents/i })).toHaveAttribute("aria-selected", "true"); + fireEvent.click(screen.getByRole("button", { name: "Open KB-001 plan" })); + expect(screen.getByText("Alpha document content")).toBeInTheDocument(); + fireEvent.change(screen.getByRole("textbox", { name: /search task documents/i }), { - target: { value: "alpha" }, + target: { value: "beta" }, }); await waitFor(() => { - expect(screen.getByText("KB-001")).toBeInTheDocument(); - expect(screen.queryByText("KB-002")).not.toBeInTheDocument(); + expect(screen.getByText("KB-002")).toBeInTheDocument(); + expect(screen.queryByText("KB-001")).not.toBeInTheDocument(); + expect(screen.queryByText("Alpha document content")).not.toBeInTheDocument(); + expect(screen.getByText("Select a task document to view its content.")).toBeInTheDocument(); }); }); @@ -907,6 +954,33 @@ describe("DocumentsView", () => { expect(screen.getByRole("button", { name: "Open README.md" })).toBeInTheDocument(); }); + it("supports mobile list/detail navigation for task documents", async () => { + window.innerWidth = 600; + mockUseProjectMarkdownFiles.mockReturnValue({ + files: [], + loading: false, + error: null, + refresh: vi.fn().mockResolvedValue(undefined), + }); + + render(); + + await waitFor(() => { + expect(screen.getByRole("tab", { name: /show task documents/i })).toHaveAttribute("aria-selected", "true"); + }); + + fireEvent.click(screen.getByRole("button", { name: "Open KB-001 plan" })); + + expect(screen.getByRole("button", { name: /back to task documents list/i })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Open KB-001 plan" })).not.toBeInTheDocument(); + expect(screen.getByText("Alpha document content")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: /back to task documents list/i })); + + expect(screen.getByRole("button", { name: "Open KB-001 plan" })).toBeInTheDocument(); + expect(screen.queryByText("Alpha document content")).not.toBeInTheDocument(); + }); + it("opens task details from task document groups", async () => { mockUseProjectMarkdownFiles.mockReturnValue({ files: [], @@ -1021,12 +1095,7 @@ describe("DocumentsView", () => { expect(screen.getByText("KB-001")).toBeInTheDocument(); }); - // Expand a task group - fireEvent.click(screen.getByRole("button", { name: /expand documents for task KB-001/i })); - - // Expand the document card - const expandBtn = screen.getByRole("button", { name: /expand content/i }); - fireEvent.click(expandBtn); + fireEvent.click(screen.getByRole("button", { name: "Open KB-001 plan" })); // Task document toggle should default to raw (not influenced by project toggle) const taskToggle = screen.getByRole("button", { name: /switch to markdown/i }); @@ -1041,7 +1110,7 @@ describe("DocumentsView", () => { expect(screen.getByRole("button", { name: /switch to plain text/i })).toHaveAttribute("aria-pressed", "true"); }); - it("task document cards support markdown toggle when expanded", async () => { + it("task document viewer supports markdown toggle", async () => { mockUseProjectMarkdownFiles.mockReturnValue({ files: [], loading: false, @@ -1054,12 +1123,7 @@ describe("DocumentsView", () => { fireEvent.click(screen.getByRole("tab", { name: /show task documents/i })); expect(screen.getByRole("tab", { name: /show task documents/i })).toHaveAttribute("aria-selected", "true"); - // Expand task group - fireEvent.click(screen.getByRole("button", { name: /expand documents for task KB-001/i })); - - // Expand the document card - const expandBtn = screen.getByRole("button", { name: /expand content/i }); - fireEvent.click(expandBtn); + fireEvent.click(screen.getByRole("button", { name: "Open KB-001 plan" })); // Should show raw text by default expect(screen.getByText("Alpha document content")).toBeInTheDocument();