From 52978e84ba97ce6d3ba690d807fbfa3de3301ad0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 19:07:54 -0500 Subject: [PATCH] Remove namespace from skill detail page header Per feedback, the detail page looks cleaner without the org/repo namespace line above the skill name. The icon remains on the name row. Co-Authored-By: Paperclip --- ui/src/pages/CompanySkills.tsx | 69 ++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx index d33023b6..e6f3f270 100644 --- a/ui/src/pages/CompanySkills.tsx +++ b/ui/src/pages/CompanySkills.tsx @@ -7,6 +7,7 @@ import type { CompanySkillFileDetail, CompanySkillFileInventoryEntry, CompanySkillListItem, + CompanySkillProjectScanResult, CompanySkillSourceBadge, CompanySkillUpdateStatus, } from "@paperclipai/shared"; @@ -167,6 +168,17 @@ function shortRef(ref: string | null | undefined) { return ref.slice(0, 7); } +function formatProjectScanSummary(result: CompanySkillProjectScanResult) { + const parts = [ + `${result.discovered} found`, + `${result.imported.length} imported`, + `${result.updated.length} updated`, + ]; + if (result.conflicts.length > 0) parts.push(`${result.conflicts.length} conflicts`); + if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped`); + return `${parts.join(", ")} across ${result.scannedWorkspaces} workspace${result.scannedWorkspaces === 1 ? "" : "s"}.`; +} + function fileIcon(kind: CompanySkillFileInventoryEntry["kind"]) { if (kind === "script" || kind === "reference") return FileCode2; return FileText; @@ -542,11 +554,6 @@ function SkillPane({
- {detail.key.includes("/") && ( -
- {detail.key.split("/").slice(0, -1).join("/")} -
- )}

{detail.name} @@ -734,6 +741,7 @@ export function CompanySkills() { const [draft, setDraft] = useState(""); const [displayedDetail, setDisplayedDetail] = useState(null); const [displayedFile, setDisplayedFile] = useState(null); + const [scanStatusMessage, setScanStatusMessage] = useState(null); const parsedRoute = useMemo(() => parseSkillRoute(routePath), [routePath]); const routeSkillId = parsedRoute.skillId; const selectedPath = parsedRoute.filePath; @@ -876,6 +884,45 @@ export function CompanySkills() { }, }); + const scanProjects = useMutation({ + mutationFn: () => companySkillsApi.scanProjects(selectedCompanyId!), + onMutate: () => { + setScanStatusMessage("Scanning project workspaces for skills..."); + }, + onSuccess: async (result) => { + setScanStatusMessage("Refreshing skills list..."); + await queryClient.invalidateQueries({ queryKey: queryKeys.companySkills.list(selectedCompanyId!) }); + const summary = formatProjectScanSummary(result); + setScanStatusMessage(summary); + pushToast({ + tone: "success", + title: "Project skill scan complete", + body: summary, + }); + if (result.conflicts[0]) { + pushToast({ + tone: "warn", + title: "Skill conflicts found", + body: result.conflicts[0].reason, + }); + } else if (result.warnings[0]) { + pushToast({ + tone: "warn", + title: "Scan warnings", + body: result.warnings[0], + }); + } + }, + onError: (error) => { + setScanStatusMessage(null); + pushToast({ + tone: "error", + title: "Project skill scan failed", + body: error instanceof Error ? error.message : "Failed to scan project workspaces.", + }); + }, + }); + const saveFile = useMutation({ mutationFn: () => companySkillsApi.updateFile( selectedCompanyId!, @@ -1002,10 +1049,11 @@ export function CompanySkills() {

+ {scanStatusMessage && ( +

+ {scanStatusMessage} +

+ )}
{createOpen && (