From 8fad2a654c0f5ecdab082b8c3ba865be11a0e4a3 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 5 Jul 2026 12:27:20 +0000 Subject: [PATCH] Scope folder rail counts to the page's visible items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rail/chip/sheet counts now derive from the same item set the list renders (archived routines excluded; skills scoped to category/search), per ux-spec §5.3, instead of raw DB counts. Co-Authored-By: Paperclip --- ui/src/pages/CompanySkills.tsx | 28 ++++++++++++++++++++++++++-- ui/src/pages/Routines.tsx | 26 +++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx index 7acd7c61a17..e80554703d4 100644 --- a/ui/src/pages/CompanySkills.tsx +++ b/ui/src/pages/CompanySkills.tsx @@ -4583,6 +4583,30 @@ export function CompanySkills() { : "Create an editable company skill in the Paperclip workspace."; const skillFolderResult = skillFoldersQuery.data ?? null; const showInstalledFolders = isDiscovery && discoveryTab === "installed"; + // Rail counts reflect the current category/search scope, never the folder + // filter itself (ux-spec §5.3). + const railSkillFolderResult = useMemo(() => { + if (!skillFolderResult || discoveryTab !== "installed") return skillFolderResult; + const scoped = discoveryTabCards.filter((card) => { + if (discoveryCategory && !card.categories.includes(discoveryCategory)) return false; + return discoveryMatchesSearch(card, discoverySearch.trim()); + }); + const counts = new Map(); + let unfiled = 0; + for (const card of scoped) { + if (card.folderId) counts.set(card.folderId, (counts.get(card.folderId) ?? 0) + 1); + else unfiled += 1; + } + return { + ...skillFolderResult, + allCount: scoped.length, + unfiledCount: unfiled, + folders: skillFolderResult.folders.map((folder) => ({ + ...folder, + itemCount: counts.get(folder.id) ?? 0, + })), + }; + }, [skillFolderResult, discoveryTab, discoveryTabCards, discoveryCategory, discoverySearch]); return ( <> @@ -4777,7 +4801,7 @@ export function CompanySkills() { scanProjects.mutate()} scanPending={scanProjects.isPending} scanStatus={scanStatusMessage} - folderResult={showInstalledFolders ? skillFolderResult : null} + folderResult={showInstalledFolders ? railSkillFolderResult : null} folderSelection={folderSelection} foldersLoading={skillFoldersQuery.isLoading} selectMode={showInstalledFolders && selectMode} diff --git a/ui/src/pages/Routines.tsx b/ui/src/pages/Routines.tsx index f368073dba1..6925e2803f3 100644 --- a/ui/src/pages/Routines.tsx +++ b/ui/src/pages/Routines.tsx @@ -604,6 +604,26 @@ export function Routines() { if (folderSelection === "unfiled") return visibleRoutines.filter((routine) => !routine.folderId); return visibleRoutines.filter((routine) => routine.folderId === folderSelection); }, [folderSelection, routineViewState.groupBy, visibleRoutines]); + // Rail counts reflect the page's visible scope (archived hidden), not raw DB + // counts (ux-spec §5.3). + const railFolderResult = useMemo(() => { + if (!routineFolders) return routineFolders; + const counts = new Map(); + let unfiled = 0; + for (const routine of visibleRoutines) { + if (routine.folderId) counts.set(routine.folderId, (counts.get(routine.folderId) ?? 0) + 1); + else unfiled += 1; + } + return { + ...routineFolders, + allCount: visibleRoutines.length, + unfiledCount: unfiled, + folders: routineFolders.folders.map((folder) => ({ + ...folder, + itemCount: counts.get(folder.id) ?? 0, + })), + }; + }, [routineFolders, visibleRoutines]); const sortedRoutines = useMemo( () => sortRoutines(folderFilteredRoutines, routineViewState.sortField, routineViewState.sortDir), [folderFilteredRoutines, routineViewState.sortDir, routineViewState.sortField], @@ -834,7 +854,7 @@ export function Routines() { {routineViewState.groupBy === "folder" ? (
setMobileFoldersOpen(true)} @@ -1144,7 +1164,7 @@ export function Routines() {
{showFolderRail ? (