From 08f39b4f3a076681b8d12b1334235a6bd1e86baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Wed, 10 Jun 2026 19:58:56 -0400 Subject: [PATCH] Make the Files tree show real file counts, not agent counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Spaces" section badged each department with its agent count, so an org with one CEO and zero files showed "Executive 1". Clicking it filtered to that department's files and revealed nothing — the tree advertised a folder that opened empty. collectDepartments now counts files per department and omits departments with none, so every badge equals what the section contains. Unit-tested for file-based counting and empty omission. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/renderer/src/pages/FilesPage.tsx | 2 +- .../renderer/src/pages/files/FilesTree.tsx | 2 +- .../renderer/src/pages/files/file-helpers.ts | 18 +++++--- .../tests/unit/file-helpers.test.ts | 41 +++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 AgentCompany-New/tests/unit/file-helpers.test.ts diff --git a/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx b/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx index a2108797..3d599cb0 100644 --- a/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx @@ -32,7 +32,7 @@ export function FilesPage({ snapshot, actions }: PageProps) { const [view, setView] = useState("list"); const [scanFilter, setScanFilter] = useState("all"); - const departments = collectDepartments(agents); + const departments = collectDepartments(files, agents); const filesByAgent = collectAgentFiles(files, agents); const sectionFiles = filterFiles(files, section, agents); const visibleFiles = scanFilter === "all" ? sectionFiles : sectionFiles.filter((file) => file.scanState === scanFilter); diff --git a/AgentCompany-New/src/renderer/src/pages/files/FilesTree.tsx b/AgentCompany-New/src/renderer/src/pages/files/FilesTree.tsx index 529f6406..127b87af 100644 --- a/AgentCompany-New/src/renderer/src/pages/files/FilesTree.tsx +++ b/AgentCompany-New/src/renderer/src/pages/files/FilesTree.tsx @@ -91,7 +91,7 @@ function TreeItem({ }) { return ( diff --git a/AgentCompany-New/src/renderer/src/pages/files/file-helpers.ts b/AgentCompany-New/src/renderer/src/pages/files/file-helpers.ts index 44849ba0..edac1934 100644 --- a/AgentCompany-New/src/renderer/src/pages/files/file-helpers.ts +++ b/AgentCompany-New/src/renderer/src/pages/files/file-helpers.ts @@ -23,13 +23,19 @@ export const FILE_COLOR_MAP: Record = folder: { color: "#a1a1aa", label: "" }, }; -export function collectDepartments(agents: AgentRecord[]): Array<{ name: string; count: number }> { - const seen = new Map(); - for (const agent of agents) { - if (!agent.department) continue; - seen.set(agent.department, (seen.get(agent.department) ?? 0) + 1); +/** Departments shown in the file tree carry a FILE count, not an agent count: + * the badge must equal what opening the section reveals. Departments with no + * files are omitted so the tree never advertises a folder that opens empty. */ +export function collectDepartments(files: FileObjectRecord[], agents: AgentRecord[]): Array<{ name: string; count: number }> { + const departmentByAgent = new Map(agents.map((agent) => [agent.id, agent.department])); + const counts = new Map(); + for (const file of files) { + if (file.ownerType !== "agent" || !file.ownerId) continue; + const department = departmentByAgent.get(file.ownerId); + if (!department) continue; + counts.set(department, (counts.get(department) ?? 0) + 1); } - return [...seen.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => a.name.localeCompare(b.name)); + return [...counts.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => a.name.localeCompare(b.name)); } export function collectAgentFiles(files: FileObjectRecord[], agents: AgentRecord[]): Array<{ agent: AgentRecord; count: number }> { diff --git a/AgentCompany-New/tests/unit/file-helpers.test.ts b/AgentCompany-New/tests/unit/file-helpers.test.ts new file mode 100644 index 00000000..343ec0fc --- /dev/null +++ b/AgentCompany-New/tests/unit/file-helpers.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "vitest"; +import { collectDepartments } from "../../src/renderer/src/pages/files/file-helpers"; +import type { AgentRecord, FileObjectRecord } from "../../src/shared/domain"; + +function agent(id: string, department: string): AgentRecord { + return { + id, orgId: "org", name: id, role: "engineer", department, + modelProviderId: null, modelId: null, budgetCents: 0, managerAgentId: null, + title: id, roleLevel: "ic", modelStrategy: "single", workspaceId: null, + heartbeatEnabled: false, heartbeatIntervalSec: 0, lifecycle: "active", createdAt: "2026-01-01", + }; +} + +function file(id: string, ownerType: FileObjectRecord["ownerType"], ownerId: string | null): FileObjectRecord { + return { + id, orgId: "org", name: id, kind: "doc", sizeBytes: 1, sha256: "x", storagePath: `/tmp/${id}`, + ownerType, ownerId, spaceId: null, pinned: false, scanState: "approved", scanLabelsJson: "[]", + scannedAt: null, createdAt: "2026-01-01", updatedAt: null, + }; +} + +describe("collectDepartments", () => { + test("counts files per department, not agents, and omits empty departments", () => { + const agents = [agent("a1", "Engineering"), agent("a2", "Engineering"), agent("a3", "Design"), agent("a4", "Sales")]; + const files = [ + file("f1", "agent", "a1"), + file("f2", "agent", "a1"), + file("f3", "agent", "a3"), + file("f4", "employee", "emp1"), // not owned by an agent → ignored + ]; + // Engineering has two agents but only a1 holds files (2); Design has 1; Sales has agents but no files. + expect(collectDepartments(files, agents)).toEqual([ + { name: "Design", count: 1 }, + { name: "Engineering", count: 2 }, + ]); + }); + + test("returns nothing when no agent owns a file", () => { + expect(collectDepartments([], [agent("a1", "Engineering")])).toEqual([]); + }); +});