From 2a6df2e796d7b5ee79e8854011690a100453895a Mon Sep 17 00:00:00 2001 From: morgmart <98432065+morgmart@users.noreply.github.com> Date: Tue, 5 May 2026 11:47:35 -0700 Subject: [PATCH] remove skill categories (#9008) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> --- .../features/skills/lib/skillCategories.ts | 294 ------------------ .../src/features/skills/lib/skillsHelpers.ts | 22 +- .../skills/ui/SkillCategoryFilter.tsx | 93 ------ .../features/skills/ui/SkillDetailPage.tsx | 11 +- .../features/skills/ui/SkillsListSections.tsx | 6 +- .../src/features/skills/ui/SkillsToolbar.tsx | 15 - .../src/features/skills/ui/SkillsView.tsx | 41 +-- .../skills/ui/__tests__/SkillsView.test.tsx | 18 -- .../src/shared/i18n/locales/en/skills.json | 18 -- .../src/shared/i18n/locales/es/skills.json | 18 -- ui/goose2/tests/e2e/skills.spec.ts | 21 -- 11 files changed, 16 insertions(+), 541 deletions(-) delete mode 100644 ui/goose2/src/features/skills/lib/skillCategories.ts delete mode 100644 ui/goose2/src/features/skills/ui/SkillCategoryFilter.tsx diff --git a/ui/goose2/src/features/skills/lib/skillCategories.ts b/ui/goose2/src/features/skills/lib/skillCategories.ts deleted file mode 100644 index 80ef6a3a..00000000 --- a/ui/goose2/src/features/skills/lib/skillCategories.ts +++ /dev/null @@ -1,294 +0,0 @@ -import type { SkillInfo } from "../api/skills"; - -const SKILL_CATEGORY_ORDER = [ - "design", - "engineering", - "quality", - "research", - "writing", - "integrations", - "operations", - "productivity", - "general", -] as const; - -export type SkillCategory = (typeof SKILL_CATEGORY_ORDER)[number]; - -export interface SkillViewInfo extends SkillInfo { - inferredCategory: SkillCategory; -} - -const DESIGN_SLUGS = new Set([ - "adapt", - "animate", - "audit", - "bolder", - "clarify", - "colorize", - "critique", - "delight", - "distill", - "frontend-design", - "harden", - "impeccable", - "layout", -]); - -const ENGINEERING_SLUGS = new Set([ - "cloning-squareup-repos", - "create-pr", - "plugin-creator", - "skill-creator", - "skill-installer", -]); - -const QUALITY_SLUGS = new Set([ - "code-review", - "create-app-e2e-test", - "edge-case-finder", -]); - -const RESEARCH_SLUGS = new Set([ - "agent-browser", - "codesearch", - "dev-guides", - "eng-ai-chat", - "go-link", - "openai-docs", -]); - -const WRITING_SLUGS = new Set(["ceo-weekly-update"]); - -const OPERATIONS_SLUGS = new Set([ - "check-ci", - "datadog", - "github:gh-address-comments", - "github:gh-fix-ci", -]); - -const INTEGRATION_SLUGS = new Set([ - "excel", - "gdrive", - "github:github", - "launchdarkly", - "linear", - "powerpoint", -]); - -const PRODUCTIVITY_SLUGS = new Set(["grocery-list-organizer"]); - -const CATEGORY_KEYWORDS: Record = { - design: [ - "accessibility", - "animation", - "breakpoint", - "color", - "copy", - "design", - "frontend", - "interface", - "layout", - "mobile", - "motion", - "polish", - "responsive", - "spacing", - "theme", - "typography", - "ui", - "ux", - "visual", - ], - engineering: [ - "app", - "build", - "codebase", - "create", - "feature", - "implement", - "install", - "plugin", - "react", - "repository", - "rust", - "scaffold", - "skill", - "typescript", - ], - quality: [ - "bug", - "coverage", - "edge case", - "lint", - "quality", - "regression", - "review", - "test", - "verify", - ], - research: [ - "browse", - "discover", - "docs", - "documentation", - "find", - "guide", - "investigate", - "knowledge", - "look up", - "query", - "read", - "search", - ], - writing: [ - "copy", - "document", - "draft", - "edit", - "email", - "message", - "rewrite", - "summary", - "update", - "write", - ], - integrations: [ - "drive", - "excel", - "extension", - "github", - "google docs", - "google drive", - "google sheets", - "google slides", - "launchdarkly", - "linear", - "powerpoint", - "sheets", - "slides", - ], - operations: [ - "buildkite", - "canary", - "ci", - "flag", - "incident", - "kochiku", - "log", - "metric", - "monitor", - "observability", - "release", - "trace", - ], - productivity: [ - "grocery", - "meal plan", - "organize", - "organizer", - "shopping", - "weekly update", - ], - general: [], -}; - -function normalizeText(value: string) { - return value.toLowerCase().replace(/[^a-z0-9:+\s-]/g, " "); -} - -function keywordScore(haystack: string, keywords: string[]) { - return keywords.reduce((score, keyword) => { - if (!haystack.includes(keyword)) { - return score; - } - - return score + (keyword.includes(" ") ? 2 : 1); - }, 0); -} - -function inferCategoryFromSlug(slug: string): SkillCategory | null { - if (DESIGN_SLUGS.has(slug)) { - return "design"; - } - - if (QUALITY_SLUGS.has(slug)) { - return "quality"; - } - - if (ENGINEERING_SLUGS.has(slug)) { - return "engineering"; - } - - if (RESEARCH_SLUGS.has(slug)) { - return "research"; - } - - if (WRITING_SLUGS.has(slug)) { - return "writing"; - } - - if (OPERATIONS_SLUGS.has(slug)) { - return "operations"; - } - - if (INTEGRATION_SLUGS.has(slug) || slug.startsWith("google-drive:")) { - return "integrations"; - } - - if (PRODUCTIVITY_SLUGS.has(slug)) { - return "productivity"; - } - - return null; -} - -function inferSkillCategory( - skill: Pick, -): SkillCategory { - const slug = skill.name.toLowerCase(); - const explicitCategory = inferCategoryFromSlug(slug); - if (explicitCategory) { - return explicitCategory; - } - - const haystack = normalizeText( - [skill.name, skill.description, skill.instructions].join(" "), - ); - let bestCategory: SkillCategory = "general"; - let bestScore = 0; - - for (const category of SKILL_CATEGORY_ORDER) { - if (category === "general") { - continue; - } - - const score = keywordScore(haystack, CATEGORY_KEYWORDS[category]); - if (score > bestScore) { - bestCategory = category; - bestScore = score; - } - } - - return bestScore > 0 ? bestCategory : "general"; -} - -function withInferredSkillCategory(skill: SkillInfo): SkillViewInfo { - return { - ...skill, - inferredCategory: inferSkillCategory(skill), - }; -} - -export function withInferredSkillCategories( - skills: SkillInfo[], -): SkillViewInfo[] { - return skills.map(withInferredSkillCategory); -} - -export function uniqueSkillCategories( - skills: SkillViewInfo[], -): SkillCategory[] { - return SKILL_CATEGORY_ORDER.filter((category) => - skills.some((skill) => skill.inferredCategory === category), - ); -} diff --git a/ui/goose2/src/features/skills/lib/skillsHelpers.ts b/ui/goose2/src/features/skills/lib/skillsHelpers.ts index 1f6f9f2e..8bafa393 100644 --- a/ui/goose2/src/features/skills/lib/skillsHelpers.ts +++ b/ui/goose2/src/features/skills/lib/skillsHelpers.ts @@ -1,12 +1,11 @@ import type { SkillInfo } from "../api/skills"; -import type { SkillCategory, SkillViewInfo } from "./skillCategories"; export type SkillsFilter = "all" | "global" | `project:${string}`; export interface SkillsSection { id: string; title: string; - skills: SkillViewInfo[]; + skills: SkillInfo[]; } // Mirrors crates/goose/src/skills/mod.rs::validate_skill_name. @@ -59,24 +58,19 @@ export function compareSkillsByName(a: SkillInfo, b: SkillInfo) { } export function filterSkills( - skills: SkillViewInfo[], + skills: SkillInfo[], filters: { search: string; activeFilter: SkillsFilter; - selectedCategories: SkillCategory[]; }, - getCategoryLabel: (category: SkillCategory) => string, -): SkillViewInfo[] { +): SkillInfo[] { const searchTerm = filters.search.trim().toLowerCase(); return skills.filter((skill) => { const matchesSearch = searchTerm.length === 0 || skill.name.toLowerCase().includes(searchTerm) || skill.description.toLowerCase().includes(searchTerm) || - skill.sourceLabel.toLowerCase().includes(searchTerm) || - getCategoryLabel(skill.inferredCategory) - .toLowerCase() - .includes(searchTerm); + skill.sourceLabel.toLowerCase().includes(searchTerm); const matchesFilter = filters.activeFilter === "all" @@ -87,16 +81,12 @@ export function filterSkills( (project) => `project:${project.id}` === filters.activeFilter, ); - const matchesCategory = - filters.selectedCategories.length === 0 || - filters.selectedCategories.includes(skill.inferredCategory); - - return matchesSearch && matchesFilter && matchesCategory; + return matchesSearch && matchesFilter; }); } export function groupSkills( - filteredSkills: SkillViewInfo[], + filteredSkills: SkillInfo[], activeFilter: SkillsFilter, projectFilters: { id: string; name: string }[], labels: { personalTitle: string; projectsFallback: string }, diff --git a/ui/goose2/src/features/skills/ui/SkillCategoryFilter.tsx b/ui/goose2/src/features/skills/ui/SkillCategoryFilter.tsx deleted file mode 100644 index 005405a3..00000000 --- a/ui/goose2/src/features/skills/ui/SkillCategoryFilter.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { - IconAdjustmentsHorizontal, - IconChevronDown, -} from "@tabler/icons-react"; -import { Button } from "@/shared/ui/button"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/shared/ui/dropdown-menu"; -import type { SkillCategory } from "../lib/skillCategories"; - -interface SkillCategoryFilterProps { - categories: SkillCategory[]; - selectedCategories: SkillCategory[]; - onSelectedCategoriesChange: (categories: SkillCategory[]) => void; -} - -export function SkillCategoryFilter({ - categories, - selectedCategories, - onSelectedCategoriesChange, -}: SkillCategoryFilterProps) { - const { t } = useTranslation(["skills"]); - - const toggleCategory = useCallback( - (category: SkillCategory) => { - onSelectedCategoriesChange( - selectedCategories.includes(category) - ? selectedCategories.filter((value) => value !== category) - : [...selectedCategories, category], - ); - }, - [onSelectedCategoriesChange, selectedCategories], - ); - - const buttonLabel = - selectedCategories.length === 0 - ? t("view.categories.label") - : selectedCategories.length === 1 - ? t(`view.categories.options.${selectedCategories[0]}`) - : t("view.categories.count", { count: selectedCategories.length }); - - return ( - - - - - - {t("view.categories.label")} - - {categories.map((category) => ( - event.preventDefault()} - onCheckedChange={() => toggleCategory(category)} - > - {t(`view.categories.options.${category}`)} - - ))} - {selectedCategories.length > 0 ? ( - <> - - { - event.preventDefault(); - onSelectedCategoriesChange([]); - }} - > - {t("view.categories.clear")} - - - ) : null} - - - ); -} diff --git a/ui/goose2/src/features/skills/ui/SkillDetailPage.tsx b/ui/goose2/src/features/skills/ui/SkillDetailPage.tsx index af466449..f657b574 100644 --- a/ui/goose2/src/features/skills/ui/SkillDetailPage.tsx +++ b/ui/goose2/src/features/skills/ui/SkillDetailPage.tsx @@ -21,10 +21,9 @@ import { PageColumns } from "@/shared/ui/page-columns"; import { DetailPageShell, PageHeader } from "@/shared/ui/page-shell"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; import type { SkillInfo } from "../api/skills"; -import type { SkillViewInfo } from "../lib/skillCategories"; interface SkillDetailPageProps { - skill: SkillViewInfo | null; + skill: SkillInfo | null; onBack: () => void; onEdit: (skill: SkillInfo) => void; onReveal: (skill: SkillInfo) => void; @@ -180,14 +179,6 @@ export function SkillDetailPage({ sidebar={