From 71d30b835bf2602ea725037896c96e236677bd5f Mon Sep 17 00:00:00 2001 From: Smartsheet-JB-Brown Date: Mon, 14 Apr 2025 12:37:27 -0700 Subject: [PATCH] documentation start --- cline_docs/package-manager/README.md | 22 + .../implementation/01-architecture.md | 388 +++++++ .../implementation/02-core-components.md | 533 ++++++++++ .../implementation/03-data-structures.md | 670 ++++++++++++ .../implementation/04-search-and-filter.md | 754 ++++++++++++++ .../implementation/05-ui-components.md | 878 ++++++++++++++++ .../implementation/06-testing-strategy.md | 885 ++++++++++++++++ .../implementation/07-extending.md | 956 ++++++++++++++++++ .../localization-improvements.md | 397 ++++++++ .../user-guide/01-introduction.md | 54 + .../user-guide/02-browsing-packages.md | 138 +++ .../user-guide/03-searching-and-filtering.md | 132 +++ .../user-guide/04-working-with-details.md | 139 +++ .../user-guide/05-adding-packages.md | 6 + .../user-guide/06-adding-custom-sources.md | 198 ++++ 15 files changed, 6150 insertions(+) create mode 100644 cline_docs/package-manager/README.md create mode 100644 cline_docs/package-manager/implementation/01-architecture.md create mode 100644 cline_docs/package-manager/implementation/02-core-components.md create mode 100644 cline_docs/package-manager/implementation/03-data-structures.md create mode 100644 cline_docs/package-manager/implementation/04-search-and-filter.md create mode 100644 cline_docs/package-manager/implementation/05-ui-components.md create mode 100644 cline_docs/package-manager/implementation/06-testing-strategy.md create mode 100644 cline_docs/package-manager/implementation/07-extending.md create mode 100644 cline_docs/package-manager/implementation/localization-improvements.md create mode 100644 cline_docs/package-manager/user-guide/01-introduction.md create mode 100644 cline_docs/package-manager/user-guide/02-browsing-packages.md create mode 100644 cline_docs/package-manager/user-guide/03-searching-and-filtering.md create mode 100644 cline_docs/package-manager/user-guide/04-working-with-details.md create mode 100644 cline_docs/package-manager/user-guide/05-adding-packages.md create mode 100644 cline_docs/package-manager/user-guide/06-adding-custom-sources.md diff --git a/cline_docs/package-manager/README.md b/cline_docs/package-manager/README.md new file mode 100644 index 0000000000..ef969180c8 --- /dev/null +++ b/cline_docs/package-manager/README.md @@ -0,0 +1,22 @@ +# Package Manager Documentation + +This directory contains comprehensive documentation for the Roo Code Package Manager feature, including both user guides and implementation documentation. + +## Table of Contents + +### User Guide +1. [Introduction to Package Manager](./user-guide/01-introduction.md) +2. [Browsing Packages](./user-guide/02-browsing-packages.md) +3. [Searching and Filtering](./user-guide/03-searching-and-filtering.md) +4. [Working with Package Details](./user-guide/04-working-with-details.md) +5. [Adding Packages](./user-guide/05-adding-packages.md) +6. [Adding Custom Package Sources](./user-guide/06-adding-custom-sources.md) + +### Implementation Documentation +1. [Package Manager Architecture](./implementation/01-architecture.md) +2. [Core Components](./implementation/02-core-components.md) +3. [Data Structures](./implementation/03-data-structures.md) +4. [Search and Filter Implementation](./implementation/04-search-and-filter.md) +5. [UI Component Design](./implementation/05-ui-components.md) +6. [Testing Strategy](./implementation/06-testing-strategy.md) +7. [Extending the Package Manager](./implementation/07-extending.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/01-architecture.md b/cline_docs/package-manager/implementation/01-architecture.md new file mode 100644 index 0000000000..954f528194 --- /dev/null +++ b/cline_docs/package-manager/implementation/01-architecture.md @@ -0,0 +1,388 @@ +# Package Manager Architecture + +This document provides a comprehensive overview of the Package Manager's architecture, including its components, interactions, and data flow. + +## System Overview + +The Package Manager is built on a modular architecture that separates concerns between data management, UI rendering, and user interactions. The system consists of several key components that work together to provide a seamless experience for discovering, browsing, and managing packages. + +### High-Level Architecture + +```mermaid +graph TD + User[User] -->|Interacts with| UI[Package Manager UI] + UI -->|Sends messages| MH[Message Handler] + MH -->|Processes requests| PM[PackageManagerManager] + PM -->|Loads data from| MS[MetadataScanner] + MS -->|Reads| FS[File System / Git Repositories] + PM -->|Returns filtered data| MH + MH -->|Updates state| UI + UI -->|Displays| User +``` + +The architecture follows a message-based pattern where: + +1. The UI sends messages to the backend through a message handler +2. The backend processes these messages and returns results +3. The UI updates based on the returned data +4. Components are loosely coupled through message passing + +## Component Interactions + +The Package Manager components interact through a well-defined message flow: + +### Core Interaction Patterns + +1. **Data Loading**: + - MetadataScanner loads package data from repositories + - PackageManagerManager stores and manages this data + - UI requests data through the message handler + +2. **Filtering and Search**: + - UI sends filter/search criteria to the backend + - PackageManagerManager applies filters to the data + - Filtered results are returned to the UI + +3. **Source Management**: + - UI sends source management commands + - PackageManagerManager updates source configurations + - MetadataScanner reloads data from updated sources + +## Data Flow Diagram + +The following diagram illustrates the data flow through the Package Manager system: + +```mermaid +graph LR + subgraph Sources + GR[Git Repositories] + FS[File System] + end + + subgraph Backend + MS[MetadataScanner] + PM[PackageManagerManager] + MH[Message Handler] + end + + subgraph Frontend + UI[UI Components] + State[State Management] + end + + GR -->|Raw Data| MS + FS -->|Template Data| MS + MS -->|Parsed Metadata| PM + PM -->|Stored Items| PM + UI -->|User Actions| MH + MH -->|Messages| PM + PM -->|Filtered Data| MH + MH -->|Updates| State + State -->|Renders| UI +``` + +## Sequence Diagrams + +### Package Loading Sequence + +The following sequence diagram shows how packages are loaded from sources: + +```mermaid +sequenceDiagram + participant User + participant UI as UI Components + participant MH as Message Handler + participant PM as PackageManagerManager + participant MS as MetadataScanner + participant FS as File System/Git + + User->>UI: Open Package Manager + UI->>MH: Send init message + MH->>PM: Initialize + PM->>MS: Request metadata scan + MS->>FS: Read repository data + FS-->>MS: Return raw data + MS-->>PM: Return parsed metadata + PM-->>MH: Return initial items + MH-->>UI: Update with items + UI-->>User: Display packages +``` + +### Search and Filter Sequence + +This sequence diagram illustrates the search and filter process: + +```mermaid +sequenceDiagram + participant User + participant UI as UI Components + participant MH as Message Handler + participant PM as PackageManagerManager + + User->>UI: Enter search term + UI->>MH: Send search message + MH->>PM: Apply search filter + PM->>PM: Filter items + PM-->>MH: Return filtered items + MH-->>UI: Update with filtered items + UI-->>User: Display filtered results + + User->>UI: Select type filter + UI->>MH: Send type filter message + MH->>PM: Apply type filter + PM->>PM: Filter by type + PM-->>MH: Return type-filtered items + MH-->>UI: Update with type-filtered items + UI-->>User: Display type-filtered results +``` + +## Class Diagrams + +### Core Classes + +The following class diagram shows the main classes in the Package Manager system: + +```mermaid +classDiagram + class PackageManagerManager { + -currentItems: PackageManagerItem[] + -sources: PackageManagerSource[] + +getItems(): PackageManagerItem[] + +filterItems(filters): PackageManagerItem[] + +addSource(url): void + +removeSource(url): void + +refreshSources(): void + } + + class MetadataScanner { + +scanDirectory(path): PackageManagerItem[] + +scanRepository(url): PackageManagerItem[] + -parseMetadata(file): any + -buildComponentHierarchy(items): PackageManagerItem[] + } + + class PackageManagerMessageHandler { + +handleMessage(message): any + -handleSearchMessage(message): any + -handleFilterMessage(message): any + -handleSourceMessage(message): any + } + + class PackageManagerItem { + +name: string + +description: string + +type: string + +items: any[] + +tags: string[] + +matchInfo: MatchInfo + } + + class PackageManagerSource { + +url: string + +name: string + +enabled: boolean + } + + PackageManagerManager --> PackageManagerItem: manages + PackageManagerManager --> PackageManagerSource: configures + PackageManagerManager --> MetadataScanner: uses + PackageManagerMessageHandler --> PackageManagerManager: calls +``` + +### UI Component Classes + +This class diagram shows the main UI components: + +```mermaid +classDiagram + class PackageManagerView { + -items: PackageManagerItem[] + -filters: Filters + -activeTab: string + +render(): JSX + +handleFilterChange(): void + +handleSearch(): void + } + + class PackageManagerItemCard { + -item: PackageManagerItem + -filters: Filters + +render(): JSX + +handleTagClick(): void + } + + class ExpandableSection { + -title: string + -isExpanded: boolean + +toggle(): void + +render(): JSX + } + + class TypeGroup { + -type: string + -items: any[] + -searchTerm: string + +render(): JSX + } + + PackageManagerView --> PackageManagerItemCard: contains + PackageManagerItemCard --> ExpandableSection: contains + ExpandableSection --> TypeGroup: contains +``` + +## Component Responsibilities + +### Backend Components + +1. **MetadataScanner** + - Scans directories and repositories for package metadata + - Parses YAML metadata files + - Builds component hierarchies + - Handles file system and Git operations + +2. **PackageManagerManager** + - Stores and manages package items + - Applies filters and search criteria + - Manages package sources + - Handles package operations + +3. **packageManagerMessageHandler** + - Routes messages between UI and backend + - Processes commands from the UI + - Returns data and status updates to the UI + - Handles error conditions + +### Frontend Components + +1. **PackageManagerView** + - Main container component + - Manages overall UI state + - Handles tab navigation + - Displays filter controls + +2. **PackageManagerItemCard** + - Displays individual package information + - Handles tag interactions + - Manages expandable details section + - Provides action buttons + +3. **ExpandableSection** + - Provides collapsible UI sections + - Manages expand/collapse state + - Handles animations + - Displays section headers and badges + +4. **TypeGroup** + - Groups and displays components by type + - Formats item lists + - Highlights search matches + - Provides consistent styling + +## Data Flow Patterns + +### Message-Based Communication + +The Package Manager uses a message-based architecture for communication between the frontend and backend: + +1. **Message Structure**: + ```typescript + { + type: string; // The message type (e.g., "search", "filter", "addSource") + payload: any; // The message data + } + ``` + +2. **Common Message Types**: + - `search`: Apply a search term filter + - `filter`: Apply type or tag filters + - `addSource`: Add a new package source + - `removeSource`: Remove a package source + - `refreshSources`: Reload data from sources + +3. **Response Structure**: + ```typescript + { + type: string; // The response type + data: any; // The response data + error?: string; // Optional error message + } + ``` + +### State Management + +The Package Manager maintains state in several places: + +1. **Backend State**: + - Current items in the PackageManagerManager + - Source configurations + - Cached metadata + +2. **Frontend State**: + - Current filters and search terms + - UI state (active tab, expanded sections) + - Display preferences + +3. **Persistent State**: + - Source configurations stored in extension settings + - User preferences + +## Performance Considerations + +The Package Manager architecture addresses several performance challenges: + +1. **Lazy Loading**: + - Metadata is loaded on demand + - Repositories are scanned only when needed + - UI components render incrementally + +2. **Efficient Filtering**: + - Filtering happens on the backend to reduce data transfer + - Search algorithms optimize for common patterns + - Results are cached when possible + +3. **Responsive UI**: + - Asynchronous operations prevent UI blocking + - Animations provide feedback during loading + - Pagination limits the number of items displayed at once + +## Error Handling + +The architecture includes robust error handling: + +1. **Source Errors**: + - Invalid repositories are marked with error states + - Users are notified of access issues + - The system continues to function with other sources + +2. **Parsing Errors**: + - Malformed metadata is gracefully handled + - Partial results are displayed when possible + - Error details are logged for debugging + +3. **Network Errors**: + - Timeouts and retries for network operations + - Offline mode with cached data + - Clear error messages for user troubleshooting + +## Extensibility Points + +The Package Manager architecture is designed for extensibility: + +1. **New Component Types**: + - The system can be extended to support new component types + - Type-specific rendering can be added to the UI + - Backend processing adapts to new types + +2. **Additional Filters**: + - New filter types can be added to the system + - Filter logic can be extended in the PackageManagerManager + - UI can be updated to display new filter controls + +3. **Custom Sources**: + - The source system supports various repository types + - Custom source providers can be implemented + - Authentication mechanisms can be extended + +--- + +**Previous**: [Adding Custom Package Sources](../user-guide/06-adding-custom-sources.md) | **Next**: [Core Components](./02-core-components.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/02-core-components.md b/cline_docs/package-manager/implementation/02-core-components.md new file mode 100644 index 0000000000..c731e538bc --- /dev/null +++ b/cline_docs/package-manager/implementation/02-core-components.md @@ -0,0 +1,533 @@ +# Core Components + +This document provides detailed information about the core components of the Package Manager system, their responsibilities, implementation details, and interactions. + +## MetadataScanner + +The MetadataScanner is responsible for reading and parsing package metadata from various sources, including local file systems and remote Git repositories. + +### Responsibilities + +- Scanning directories for package metadata files +- Parsing YAML metadata into structured objects +- Building component hierarchies +- Handling file system and Git operations +- Supporting localized metadata + +### Implementation Details + +```typescript +class MetadataScanner { + /** + * Scans a directory for package metadata + * @param directoryPath Path to the directory to scan + * @param baseUrl Base URL for the repository (for remote sources) + * @returns Array of package items + */ + public async scanDirectory(directoryPath: string, baseUrl?: string): Promise { + // Implementation details + } + + /** + * Scans a Git repository for package metadata + * @param repoUrl URL of the Git repository + * @returns Array of package items + */ + public async scanRepository(repoUrl: string): Promise { + // Implementation details + } + + /** + * Parses a YAML metadata file + * @param filePath Path to the metadata file + * @returns Parsed metadata object + */ + private async parseMetadataFile(filePath: string): Promise { + // Implementation details + } + + /** + * Builds a component hierarchy from flat items + * @param items Array of items to organize + * @returns Hierarchical structure of items + */ + private buildComponentHierarchy(items: any[]): PackageManagerItem[] { + // Implementation details + } +} +``` + +### Key Algorithms + +#### Directory Scanning + +The directory scanning algorithm recursively traverses directories looking for metadata files: + +1. Start at the root directory +2. Look for `metadata.*.yml` files in the current directory +3. Parse found metadata files +4. For each subdirectory: + - Determine the component type based on directory name + - Recursively scan the subdirectory + - Associate child components with parent components +5. Build the component hierarchy + +#### Metadata Parsing + +The metadata parsing process handles multiple formats and localizations: + +1. Read the YAML file content +2. Parse the YAML into a JavaScript object +3. Extract the locale from the filename (e.g., `en` from `metadata.en.yml`) +4. Validate required fields (name, description, version) +5. Process optional fields (tags, author, etc.) +6. Return a structured metadata object + +### Error Handling + +The MetadataScanner includes robust error handling: + +- Invalid YAML files are reported with specific parsing errors +- Missing required fields trigger validation errors +- File system access issues are caught and reported +- Network errors during Git operations are handled gracefully +- Partial results are returned when possible, with error flags + +## PackageManagerManager + +The PackageManagerManager is the central component that manages package items, applies filters, and handles package operations. + +### Responsibilities + +- Storing and managing package items +- Applying filters and search criteria +- Managing package sources +- Handling package operations +- Maintaining state between sessions + +### Implementation Details + +```typescript +class PackageManagerManager { + private currentItems: PackageManagerItem[] = []; + private sources: PackageManagerSource[] = []; + + /** + * Constructor + * @param context VS Code extension context + */ + constructor(private context: vscode.ExtensionContext) { + // Initialize from stored state + } + + /** + * Get all items + * @returns Array of all package items + */ + public getItems(): PackageManagerItem[] { + return this.currentItems; + } + + /** + * Filter items based on criteria + * @param filters Filter criteria + * @returns Filtered array of items + */ + public filterItems(filters: { type?: string; search?: string; tags?: string[] }): PackageManagerItem[] { + // Implementation details + } + + /** + * Add a new package source + * @param url Source repository URL + * @param name Optional source name + * @returns Success status + */ + public async addSource(url: string, name?: string): Promise { + // Implementation details + } + + /** + * Remove a package source + * @param url Source repository URL + * @returns Success status + */ + public removeSource(url: string): boolean { + // Implementation details + } + + /** + * Refresh all sources + * @returns Updated items + */ + public async refreshSources(): Promise { + // Implementation details + } + + /** + * Save state to persistent storage + */ + private saveState(): void { + // Implementation details + } +} +``` + +### Key Algorithms + +#### Item Filtering + +The filtering algorithm applies multiple criteria to the package items: + +1. Start with the complete set of items +2. If a type filter is specified: + - Keep only items matching the specified type +3. If a search term is specified: + - Check item name, description, and author for matches + - Check subcomponents for matches + - Keep items that match or have matching subcomponents + - Add match information to the items +4. If tag filters are specified: + - Keep only items that have at least one of the specified tags +5. Return the filtered items with match information + +#### Source Management + +The source management process handles adding, removing, and refreshing sources: + +1. For adding a source: + - Validate the repository URL + - Check if the source already exists + - Add the source to the list + - Scan the repository for items + - Add the items to the current set + - Save the updated source list + +2. For removing a source: + - Find the source in the list + - Remove items from that source + - Remove the source from the list + - Save the updated source list + +3. For refreshing sources: + - Clear the current items + - For each enabled source: + - Scan the repository for items + - Add the items to the current set + - Return the updated items + +### State Persistence + +The PackageManagerManager maintains state between sessions: + +- Source configurations are stored in extension global state +- User preferences are persisted +- Cached metadata can be stored for performance +- State is loaded during initialization +- State is saved after significant changes + +## packageManagerMessageHandler + +The packageManagerMessageHandler is responsible for routing messages between the UI and the backend components. + +### Responsibilities + +- Processing messages from the UI +- Calling appropriate PackageManagerManager methods +- Returning results to the UI +- Handling errors and status updates +- Managing asynchronous operations + +### Implementation Details + +```typescript +/** + * Handle package manager messages + * @param message The message to handle + * @param packageManager The package manager instance + * @returns Response object + */ +export async function handlePackageManagerMessages( + message: any, + packageManager: PackageManagerManager +): Promise { + switch (message.type) { + case "getItems": + return { + type: "items", + data: packageManager.getItems() + }; + + case "search": + return { + type: "searchResults", + data: packageManager.filterItems({ + search: message.search, + type: message.typeFilter, + tags: message.tagFilters + }) + }; + + case "addSource": + try { + const success = await packageManager.addSource(message.url, message.name); + return { + type: "sourceAdded", + data: { success } + }; + } catch (error) { + return { + type: "error", + error: error.message + }; + } + + // Additional message handlers... + + default: + return { + type: "error", + error: `Unknown message type: ${message.type}` + }; + } +} +``` + +### Message Types + +The message handler processes several types of messages: + +#### Input Messages + +1. **getItems**: Request all package items + ```typescript + { type: "getItems" } + ``` + +2. **search**: Apply search and filter criteria + ```typescript + { + type: "search", + search: "search term", + typeFilter: "mode", + tagFilters: ["tag1", "tag2"] + } + ``` + +3. **addSource**: Add a new package source + ```typescript + { + type: "addSource", + url: "https://github.com/username/repo.git", + name: "Custom Source" + } + ``` + +4. **removeSource**: Remove a package source + ```typescript + { + type: "removeSource", + url: "https://github.com/username/repo.git" + } + ``` + +5. **refreshSources**: Refresh all sources + ```typescript + { type: "refreshSources" } + ``` + +#### Output Messages + +1. **items**: Response with all items + ```typescript + { + type: "items", + data: [/* package items */] + } + ``` + +2. **searchResults**: Response with filtered items + ```typescript + { + type: "searchResults", + data: [/* filtered items */] + } + ``` + +3. **sourceAdded**: Response after adding a source + ```typescript + { + type: "sourceAdded", + data: { success: true } + } + ``` + +4. **error**: Error response + ```typescript + { + type: "error", + error: "Error message" + } + ``` + +### Asynchronous Processing + +The message handler manages asynchronous operations: + +1. Asynchronous methods return promises +2. Errors are caught and returned as error messages +3. Long-running operations can provide progress updates +4. The UI can display loading indicators during processing + +## UI Components + +The Package Manager includes several key UI components that render the interface and handle user interactions. + +### PackageManagerView + +The main container component that manages the overall UI: + +```tsx +const PackageManagerView: React.FC = () => { + const [items, setItems] = useState([]); + const [filters, setFilters] = useState({ type: "", search: "", tags: [] }); + const [activeTab, setActiveTab] = useState<"browse" | "sources">("browse"); + + // Implementation details... + + return ( +
+
+ + +
+ + {activeTab === "browse" ? ( +
+ +
+ {items.map(item => ( + + ))} +
+
+ ) : ( + + )} +
+ ); +}; +``` + +### Component Interactions + +The UI components interact through props and state: + +1. **Parent-Child Communication**: + - Parent components pass data and callbacks to children + - Children invoke callbacks to notify parents of events + +2. **State Management**: + - Component state for UI-specific state + - Shared state for filters and active tab + - Backend state accessed through messages + +3. **Event Handling**: + - UI events trigger state updates + - State updates cause re-renders + - Messages are sent to the backend when needed + +### Accessibility Features + +The UI components include several accessibility features: + +1. **Keyboard Navigation**: + - Tab order follows logical flow + - Focus indicators are visible + - Keyboard shortcuts for common actions + +2. **Screen Reader Support**: + - ARIA attributes for dynamic content + - Semantic HTML structure + - Descriptive labels and announcements + +3. **Visual Accessibility**: + - High contrast mode support + - Resizable text + - Color schemes that work with color blindness + +## Component Integration + +The core components work together to provide a complete package management experience: + +### Initialization Flow + +1. The Package Manager is activated +2. The PackageManagerManager loads stored state +3. The UI sends an initial "getItems" message +4. The message handler calls PackageManagerManager.getItems() +5. The UI receives and displays the items + +### Search and Filter Flow + +1. The user enters a search term or selects filters +2. The UI sends a "search" message with the criteria +3. The message handler calls PackageManagerManager.filterItems() +4. The PackageManagerManager applies the filters +5. The UI receives and displays the filtered items + +### Source Management Flow + +1. The user adds a new source +2. The UI sends an "addSource" message +3. The message handler calls PackageManagerManager.addSource() +4. The PackageManagerManager adds the source and scans for items +5. The UI receives confirmation and updates the display + +## Performance Optimizations + +The core components include several performance optimizations: + +1. **Lazy Loading**: + - Items are loaded on demand + - Heavy operations are deferred + - Components render incrementally + +2. **Caching**: + - Parsed metadata is cached + - Filter results can be cached + - Repository data is cached when possible + +3. **Efficient Filtering**: + - Filtering happens on the backend + - Only necessary data is transferred + - Algorithms optimize for common cases + +4. **UI Optimizations**: + - Virtual scrolling for large lists + - Debounced search input + - Optimized rendering of complex components + +--- + +**Previous**: [Package Manager Architecture](./01-architecture.md) | **Next**: [Data Structures](./03-data-structures.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/03-data-structures.md b/cline_docs/package-manager/implementation/03-data-structures.md new file mode 100644 index 0000000000..da105ce603 --- /dev/null +++ b/cline_docs/package-manager/implementation/03-data-structures.md @@ -0,0 +1,670 @@ +# Data Structures + +This document details the key data structures used in the Package Manager, including their definitions, relationships, and usage patterns. + +## Package and Component Types + +The Package Manager uses a type system to categorize different kinds of components: + +### ComponentType Enumeration + +```typescript +/** + * Supported component types + */ +export type ComponentType = "mode" | "prompt" | "package" | "mcp server"; +``` + +These types represent the different kinds of components that can be managed by the Package Manager: + +1. **mode**: AI assistant personalities with specialized capabilities +2. **prompt**: Pre-configured instructions for specific tasks +3. **package**: Collections of related components +4. **mcp server**: Model Context Protocol servers that provide additional functionality + +The type system is extensible, allowing for new component types to be added in the future. + +## Metadata Interfaces + +The Package Manager uses a set of interfaces to define the structure of metadata for different components: + +### BaseMetadata + +```typescript +/** + * Base metadata interface + */ +export interface BaseMetadata { + name: string; + description: string; + version: string; + tags?: string[]; +} +``` + +This interface defines the common properties shared by all metadata types: + +- **name**: The display name of the component +- **description**: A detailed explanation of the component's purpose +- **version**: The semantic version number +- **tags**: Optional array of relevant keywords + +### RepositoryMetadata + +```typescript +/** + * Repository root metadata + */ +export interface RepositoryMetadata extends BaseMetadata {} +``` + +This interface represents the metadata for a package source repository. It currently inherits all properties from BaseMetadata without adding additional fields, but is defined separately to allow for future repository-specific extensions. + +### ComponentMetadata + +```typescript +/** + * Component metadata with type + */ +export interface ComponentMetadata extends BaseMetadata { + type: ComponentType; +} +``` + +This interface extends BaseMetadata to include a type field, which specifies the component type. + +### PackageMetadata + +```typescript +/** + * Package metadata with optional subcomponents + */ +export interface PackageMetadata extends ComponentMetadata { + type: "package"; + items?: { + type: ComponentType; + path: string; + metadata?: ComponentMetadata; + }[]; +} +``` + +This interface represents packages that can contain subcomponents: + +- **type**: Always "package" for this interface +- **items**: Optional array of subcomponents, each with: + - **type**: The subcomponent type + - **path**: The file system path to the subcomponent + - **metadata**: Optional metadata for the subcomponent + +### SubcomponentMetadata + +```typescript +/** + * Subcomponent metadata with parent reference + */ +export interface SubcomponentMetadata extends ComponentMetadata { + parentPackage: { + name: string; + path: string; + }; +} +``` + +This interface represents components that are part of a parent package: + +- All fields from ComponentMetadata +- **parentPackage**: Reference to the parent package + - **name**: The name of the parent package + - **path**: The file system path to the parent package + +## Item Structures + +The Package Manager uses several interfaces to represent items in the UI: + +### MatchInfo + +```typescript +/** + * Information about why an item matched search/filter criteria + */ +export interface MatchInfo { + matched: boolean; + matchReason?: { + nameMatch?: boolean; + descriptionMatch?: boolean; + tagMatch?: boolean; + hasMatchingSubcomponents?: boolean; + }; +} +``` + +This interface provides information about why an item matched search or filter criteria: + +- **matched**: Boolean indicating if the item matched +- **matchReason**: Optional object with specific match reasons + - **nameMatch**: True if the name matched + - **descriptionMatch**: True if the description matched + - **tagMatch**: True if a tag matched + - **hasMatchingSubcomponents**: True if a subcomponent matched + +### PackageManagerItem + +```typescript +/** + * Represents an individual package manager item + */ +export interface PackageManagerItem { + name: string; + description: string; + type: ComponentType; + url: string; + repoUrl: string; + sourceName?: string; + author?: string; + tags?: string[]; + version?: string; + lastUpdated?: string; + sourceUrl?: string; + items?: { + type: ComponentType; + path: string; + metadata?: ComponentMetadata; + lastUpdated?: string; + matchInfo?: MatchInfo; + }[]; + matchInfo?: MatchInfo; +} +``` + +This interface represents a complete package manager item as displayed in the UI: + +- **name**: The display name of the item +- **description**: A detailed explanation of the item's purpose +- **type**: The component type +- **url**: The URL to the item's source +- **repoUrl**: The URL to the repository containing the item +- **sourceName**: Optional name of the source repository +- **author**: Optional author name +- **tags**: Optional array of relevant keywords +- **version**: Optional semantic version number +- **lastUpdated**: Optional date of last update +- **sourceUrl**: Optional URL to additional documentation +- **items**: Optional array of subcomponents +- **matchInfo**: Optional information about search/filter matches + +### PackageManagerSource + +```typescript +/** + * Represents a Git repository source for package manager items + */ +export interface PackageManagerSource { + url: string; + name?: string; + enabled: boolean; +} +``` + +This interface represents a package source repository: + +- **url**: The URL to the Git repository +- **name**: Optional display name for the source +- **enabled**: Boolean indicating if the source is active + +### PackageManagerRepository + +```typescript +/** + * Represents a repository with its metadata and items + */ +export interface PackageManagerRepository { + metadata: RepositoryMetadata; + items: PackageManagerItem[]; + url: string; + error?: string; +} +``` + +This interface represents a complete repository with its metadata and items: + +- **metadata**: The repository metadata +- **items**: Array of items in the repository +- **url**: The URL to the repository +- **error**: Optional error message if there was a problem loading the repository + +### LocalizedMetadata + +```typescript +/** + * Utility type for metadata files with locale + */ +export type LocalizedMetadata = { + [locale: string]: T; +}; +``` + +This utility type represents metadata that can be localized to different languages: + +- **[locale: string]**: Keys are locale identifiers (e.g., "en", "fr") +- **T**: The type of metadata being localized + +## UI Component Props + +The Package Manager UI components use several prop interfaces: + +### PackageManagerItemCardProps + +```typescript +interface PackageManagerItemCardProps { + item: PackageManagerItem; + filters: { type: string; search: string; tags: string[] }; + setFilters: React.Dispatch>; + activeTab: "browse" | "sources"; + setActiveTab: React.Dispatch>; +} +``` + +This interface defines the props for the PackageManagerItemCard component: + +- **item**: The package item to display +- **filters**: The current filter state +- **setFilters**: Function to update filters +- **activeTab**: The currently active tab +- **setActiveTab**: Function to change the active tab + +### ExpandableSectionProps + +```typescript +interface ExpandableSectionProps { + title: string; + children: React.ReactNode; + className?: string; + defaultExpanded?: boolean; + badge?: string; +} +``` + +This interface defines the props for the ExpandableSection component: + +- **title**: The section header text +- **children**: The content to display when expanded +- **className**: Optional CSS class name +- **defaultExpanded**: Optional flag to set initial expanded state +- **badge**: Optional badge text to display + +### TypeGroupProps + +```typescript +interface TypeGroupProps { + type: string; + items: Array<{ + name: string; + description?: string; + metadata?: any; + path?: string; + }>; + className?: string; + searchTerm?: string; +} +``` + +This interface defines the props for the TypeGroup component: + +- **type**: The component type to display +- **items**: Array of items of this type +- **className**: Optional CSS class name +- **searchTerm**: Optional search term for highlighting matches + +## Grouped Items Structure + +The Package Manager uses a specialized structure for grouping items by type: + +### GroupedItems + +```typescript +export interface GroupedItems { + [type: string]: { + type: string; + items: Array<{ + name: string; + description?: string; + metadata?: any; + path?: string; + }>; + }; +} +``` + +This interface represents items grouped by their type: + +- **[type: string]**: Keys are component types +- **type**: The component type (redundant with the key) +- **items**: Array of items of this type + - **name**: The item name + - **description**: Optional item description + - **metadata**: Optional additional metadata + - **path**: Optional file system path + +## Filter and Sort Structures + +The Package Manager uses several structures for filtering and sorting: + +### Filters + +```typescript +interface Filters { + type: string; + search: string; + tags: string[]; +} +``` + +This interface represents the filter criteria: + +- **type**: The component type filter +- **search**: The search term +- **tags**: Array of tag filters + +### SortConfig + +```typescript +interface SortConfig { + by: string; + order: "asc" | "desc"; +} +``` + +This interface represents the sort configuration: + +- **by**: The field to sort by (e.g., "name", "author") +- **order**: The sort order ("asc" for ascending, "desc" for descending) + +## Message Structures + +The Package Manager uses a message-based architecture for communication: + +### Input Messages + +```typescript +// Get all items +{ type: "getItems" } + +// Apply search and filter criteria +{ + type: "search", + search: string, + typeFilter: string, + tagFilters: string[] +} + +// Add a new package source +{ + type: "addSource", + url: string, + name?: string +} + +// Remove a package source +{ + type: "removeSource", + url: string +} + +// Refresh all sources +{ type: "refreshSources" } +``` + +### Output Messages + +```typescript +// Response with all items +{ + type: "items", + data: PackageManagerItem[] +} + +// Response with filtered items +{ + type: "searchResults", + data: PackageManagerItem[] +} + +// Response after adding a source +{ + type: "sourceAdded", + data: { success: boolean } +} + +// Error response +{ + type: "error", + error: string +} +``` + +## Template Structure + +The Package Manager uses a specific directory structure for templates: + +### Basic Template Structure + +``` +package-manager-template/ +├── metadata.en.yml # Repository metadata +├── README.md # Repository documentation +├── packages/ # Directory for package components +│ └── data-platform/ # Example package +│ └── metadata.en.yml # Package metadata +├── modes/ # Directory for mode components +│ └── developer-mode/ # Example mode +│ └── metadata.en.yml # Mode metadata +├── mcp servers/ # Directory for MCP server components +│ ├── example-server/ # Example server +│ │ └── metadata.en.yml # Server metadata +│ └── file-analyzer/ # Another example server +│ └── metadata.en.yml # Server metadata +└── groups/ # Directory for grouping components + └── data-engineering/ # Example group + └── metadata.en.yml # Group metadata +``` + +### Metadata File Structure + +```yaml +# Repository metadata (metadata.en.yml) +name: "Package Manager Template" +description: "A template repository for creating package manager sources" +version: "1.0.0" + +# Component metadata (e.g., modes/developer-mode/metadata.en.yml) +name: "Developer Mode" +description: "A specialized mode for software development tasks" +version: "1.0.0" +type: "mode" +tags: + - development + - coding + - software +``` + +## Data Flow and Transformations + +The Package Manager transforms data through several stages: + +### From File System to Metadata + +1. Raw YAML files are read from the file system +2. YAML is parsed into JavaScript objects +3. Objects are validated against metadata interfaces +4. Localized metadata is combined into a single structure + +### From Metadata to Items + +1. Metadata objects are transformed into PackageManagerItem objects +2. File paths are converted to URLs +3. Parent-child relationships are established +4. Additional information is added (e.g., lastUpdated) + +### From Items to UI + +1. Items are filtered based on user criteria +2. Match information is added to items +3. Items are sorted according to user preferences +4. Items are grouped by type for display + +## Data Validation + +The Package Manager includes validation at several levels: + +### Metadata Validation + +```typescript +function validateMetadata(metadata: any): boolean { + // Required fields + if (!metadata.name || !metadata.description || !metadata.version) { + return false; + } + + // Type validation for components + if (metadata.type && !["mode", "prompt", "package", "mcp server"].includes(metadata.type)) { + return false; + } + + // Additional validation... + + return true; +} +``` + +### URL Validation + +```typescript +function isValidUrl(urlString: string): boolean { + try { + new URL(urlString); + return true; + } catch (e) { + return false; + } +} +``` + +### Tag Validation + +```typescript +function validateTags(tags: any[]): string[] { + if (!Array.isArray(tags)) { + return []; + } + + return tags + .filter(tag => typeof tag === "string" && tag.trim().length > 0) + .map(tag => tag.trim()); +} +``` + +## Data Relationships + +The Package Manager maintains several important relationships between data structures: + +### Parent-Child Relationships + +Packages can contain subcomponents, creating a hierarchical structure: + +``` +Package +├── Mode +├── MCP Server +├── Prompt +└── Nested Package + ├── Mode + └── MCP Server +``` + +This relationship is represented in the data structures: + +- Packages have an `items` array containing subcomponents +- Subcomponents have a `parentPackage` reference + +### Source-Item Relationships + +Items are associated with their source repositories: + +- Each item has a `repoUrl` field pointing to its source +- Sources have a list of items they provide +- When a source is disabled, its items are hidden + +### Type-Group Relationships + +Items are grouped by their type for display: + +- The `GroupedItems` interface organizes items by type +- Each type group contains items of that type +- The UI displays these groups separately + +## Serialization and Persistence + +The Package Manager serializes data for persistence: + +### Source Persistence + +```typescript +// Save sources to extension state +private saveState(): void { + this.context.globalState.update("packageManagerSources", this.sources); +} + +// Load sources from extension state +private loadState(): void { + const savedSources = this.context.globalState.get("packageManagerSources", []); + this.sources = savedSources; +} +``` + +### Metadata Caching + +```typescript +// Cache metadata to improve performance +private cacheMetadata(url: string, metadata: any): void { + const cacheKey = `metadata_${url}`; + this.context.globalState.update(cacheKey, { + timestamp: Date.now(), + data: metadata + }); +} + +// Retrieve cached metadata +private getCachedMetadata(url: string): any | null { + const cacheKey = `metadata_${url}`; + const cached = this.context.globalState.get(cacheKey); + + if (!cached || Date.now() - cached.timestamp > CACHE_TTL) { + return null; + } + + return cached.data; +} +``` + +## Data Structure Evolution + +The Package Manager's data structures are designed for evolution: + +### Versioning Strategy + +- Interfaces include version fields +- New fields are added as optional +- Breaking changes are avoided when possible +- Migration code handles legacy data formats + +### Extensibility Points + +- The ComponentType can be extended with new types +- Metadata interfaces can be extended with new fields +- The message system can handle new message types +- The UI can adapt to display new data formats + +--- + +**Previous**: [Core Components](./02-core-components.md) | **Next**: [Search and Filter Implementation](./04-search-and-filter.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/04-search-and-filter.md b/cline_docs/package-manager/implementation/04-search-and-filter.md new file mode 100644 index 0000000000..e4c285620d --- /dev/null +++ b/cline_docs/package-manager/implementation/04-search-and-filter.md @@ -0,0 +1,754 @@ +# Search and Filter Implementation + +This document details the implementation of search and filtering functionality in the Package Manager, including algorithms, optimization techniques, and performance considerations. + +## Search Algorithm + +The Package Manager implements a comprehensive search algorithm that matches user queries against multiple fields and supports hierarchical component structures. + +### Search Term Matching + +The core of the search functionality is the `containsSearchTerm` function, which checks if a string contains a search term: + +```typescript +/** + * Checks if a string contains a search term (case insensitive) + * @param text The text to search in + * @param searchTerm The term to search for + * @returns True if the text contains the search term + */ +export function containsSearchTerm(text: string | undefined, searchTerm: string): boolean { + if (!text || !searchTerm) { + return false; + } + + return text.toLowerCase().includes(searchTerm.toLowerCase()); +} +``` + +This function: +- Handles undefined inputs gracefully +- Performs case-insensitive matching +- Uses JavaScript's native `includes` method for performance + +### Item Search Implementation + +The main search function applies the search term to multiple fields: + +```typescript +/** + * Checks if an item matches a search term + * @param item The item to check + * @param searchTerm The search term + * @returns Match information + */ +function itemMatchesSearch(item: PackageManagerItem, searchTerm: string): MatchInfo { + if (!searchTerm) { + return { matched: true }; + } + + const term = searchTerm.toLowerCase(); + + // Check main item fields + const nameMatch = containsSearchTerm(item.name, term); + const descriptionMatch = containsSearchTerm(item.description, term); + const authorMatch = containsSearchTerm(item.author, term); + + // Check subcomponents + let hasMatchingSubcomponents = false; + + if (item.items?.length) { + hasMatchingSubcomponents = item.items.some(subItem => + containsSearchTerm(subItem.metadata?.name, term) || + containsSearchTerm(subItem.metadata?.description, term) + ); + + // Add match info to subcomponents + item.items.forEach(subItem => { + const subNameMatch = containsSearchTerm(subItem.metadata?.name, term); + const subDescMatch = containsSearchTerm(subItem.metadata?.description, term); + + subItem.matchInfo = { + matched: subNameMatch || subDescMatch, + matchReason: subNameMatch || subDescMatch ? { + nameMatch: subNameMatch, + descriptionMatch: subDescMatch + } : undefined + }; + }); + } + + const matched = nameMatch || descriptionMatch || authorMatch || hasMatchingSubcomponents; + + return { + matched, + matchReason: matched ? { + nameMatch, + descriptionMatch, + authorMatch, + hasMatchingSubcomponents + } : undefined + }; +} +``` + +This function: +- Checks the item's name, description, and author +- Recursively checks subcomponents +- Adds match information to both the item and its subcomponents +- Returns detailed match information + +### Search Optimization Techniques + +The search implementation includes several optimizations: + +1. **Early Termination**: + - Returns as soon as any field matches + - Avoids unnecessary checks after a match is found + +2. **Efficient String Operations**: + - Uses native string methods for performance + - Converts to lowercase once per string + - Avoids regular expressions for simple matching + +3. **Match Caching**: + - Stores match information on items + - Avoids recalculating matches for the same search term + - Clears cache when the search term changes + +4. **Lazy Evaluation**: + - Only checks subcomponents if main fields don't match + - Processes subcomponents only when necessary + +## Filter Logic + +The Package Manager implements multiple filter types that can be combined to narrow down results. + +### Type Filtering + +Type filtering restricts results to components of a specific type: + +```typescript +/** + * Filters items by type + * @param items Items to filter + * @param type Type to filter by + * @returns Filtered items + */ +function filterByType(items: PackageManagerItem[], type: string): PackageManagerItem[] { + if (!type) { + return items; + } + + return items.filter(item => item.type === type); +} +``` + +### Tag Filtering + +Tag filtering shows only items with specific tags: + +```typescript +/** + * Filters items by tags + * @param items Items to filter + * @param tags Tags to filter by + * @returns Filtered items + */ +function filterByTags(items: PackageManagerItem[], tags: string[]): PackageManagerItem[] { + if (!tags.length) { + return items; + } + + return items.filter(item => { + if (!item.tags?.length) { + return false; + } + + // Item must have at least one of the specified tags + return item.tags.some(tag => tags.includes(tag)); + }); +} +``` + +### Combined Filtering + +The main filter function combines all filter types: + +```typescript +/** + * Filters items based on criteria + * @param items Items to filter + * @param filters Filter criteria + * @returns Filtered items + */ +export function filterItems( + items: PackageManagerItem[], + filters: { type?: string; search?: string; tags?: string[] } +): PackageManagerItem[] { + if (!isFilterActive(filters)) { + return items; + } + + let result = items; + + // Apply type filter + if (filters.type) { + result = filterByType(result, filters.type); + } + + // Apply search filter + if (filters.search) { + result = result.filter(item => { + const matchInfo = itemMatchesSearch(item, filters.search!); + item.matchInfo = matchInfo; + return matchInfo.matched; + }); + } + + // Apply tag filter + if (filters.tags?.length) { + result = filterByTags(result, filters.tags); + } + + return result; +} +``` + +This function: +- Applies filters in a specific order (type, search, tags) +- Short-circuits if no filters are active +- Adds match information to items +- Returns a new array with filtered items + +### Filter Optimization Techniques + +The filter implementation includes several optimizations: + +1. **Filter Order**: + - Applies the most restrictive filters first + - Reduces the number of items for subsequent filters + - Improves performance for large datasets + +2. **Short-Circuit Evaluation**: + - Skips filtering entirely if no filters are active + - Returns early when possible + +3. **Immutable Operations**: + - Creates new arrays rather than modifying existing ones + - Ensures predictable behavior + - Supports undo/redo functionality + +4. **Selective Processing**: + - Only processes necessary fields for each filter + - Avoids redundant calculations + +## Selector Functions + +The Package Manager uses selector functions to extract and transform data for the UI: + +### Filter Status Selector + +```typescript +/** + * Checks if any filters are active + * @param filters Filter criteria + * @returns True if any filters are active + */ +export const isFilterActive = (filters: Filters): boolean => { + return !!(filters.type || filters.search || filters.tags.length > 0); +}; +``` + +### Display Items Selector + +```typescript +/** + * Gets items for display based on filters and sort config + * @param items All items + * @param filters Filter criteria + * @param sortConfig Sort configuration + * @returns Filtered and sorted items + */ +export const getDisplayedItems = ( + items: PackageManagerItem[], + filters: Filters, + sortConfig: SortConfig, +): PackageManagerItem[] => { + const filteredItems = filterItems(items, filters); + return sortItems(filteredItems, sortConfig); +}; +``` + +### Sort Function + +```typescript +/** + * Sorts items based on configuration + * @param items Items to sort + * @param config Sort configuration + * @returns Sorted items + */ +export const sortItems = (items: PackageManagerItem[], config: SortConfig): PackageManagerItem[] => { + return [...items].sort((a, b) => { + let comparison = 0; + + switch (config.by) { + case "name": + comparison = a.name.localeCompare(b.name); + break; + case "author": + comparison = (a.author || "").localeCompare(b.author || ""); + break; + case "lastUpdated": + comparison = (a.lastUpdated || "").localeCompare(b.lastUpdated || ""); + break; + default: + comparison = a.name.localeCompare(b.name); + } + + return config.order === "asc" ? comparison : -comparison; + }); +}; +``` + +## Grouping Implementation + +The Package Manager includes functionality to group items by type: + +### Group By Type Function + +```typescript +/** + * Groups package items by their type + * @param items Array of items to group + * @returns Object with items grouped by type + */ +export function groupItemsByType(items: PackageManagerItem["items"] = []): GroupedItems { + if (!items?.length) { + return {}; + } + + return items.reduce((groups: GroupedItems, item) => { + if (!item.type) { + return groups; + } + + if (!groups[item.type]) { + groups[item.type] = { + type: item.type, + items: [], + }; + } + + groups[item.type].items.push({ + name: item.metadata?.name || "Unnamed item", + description: item.metadata?.description, + metadata: item.metadata, + path: item.path, + }); + + return groups; + }, {}); +} +``` + +### Helper Functions + +```typescript +/** + * Gets the total number of items across all groups + * @param groups Grouped items object + * @returns Total number of items + */ +export function getTotalItemCount(groups: GroupedItems): number { + return Object.values(groups).reduce((total, group) => total + group.items.length, 0); +} + +/** + * Gets an array of unique types from the grouped items + * @param groups Grouped items object + * @returns Array of type strings + */ +export function getUniqueTypes(groups: GroupedItems): string[] { + return Object.keys(groups).sort(); +} +``` + +## UI Integration + +The search and filter functionality is integrated with the UI through several components: + +### Search Input Component + +```tsx +const SearchInput: React.FC<{ + value: string; + onChange: (value: string) => void; +}> = ({ value, onChange }) => { + // Debounce search input to avoid excessive filtering + const debouncedOnChange = useDebounce(onChange, 300); + + return ( +
+ + debouncedOnChange(e.target.value)} + placeholder="Search packages..." + className="search-input" + aria-label="Search packages" + /> + {value && ( + + )} +
+ ); +}; +``` + +### Type Filter Component + +```tsx +const TypeFilter: React.FC<{ + value: string; + onChange: (value: string) => void; + types: string[]; +}> = ({ value, onChange, types }) => { + return ( +
+

Filter by Type

+
+ + + {types.map((type) => ( + + ))} +
+
+ ); +}; +``` + +### Tag Filter Component + +```tsx +const TagFilter: React.FC<{ + selectedTags: string[]; + onChange: (tags: string[]) => void; + availableTags: string[]; +}> = ({ selectedTags, onChange, availableTags }) => { + const toggleTag = (tag: string) => { + if (selectedTags.includes(tag)) { + onChange(selectedTags.filter(t => t !== tag)); + } else { + onChange([...selectedTags, tag]); + } + }; + + return ( +
+

Filter by Tags

+
+ {availableTags.map((tag) => ( + + ))} +
+
+ ); +}; +``` + +## Performance Considerations + +The search and filter implementation includes several performance optimizations: + +### Large Dataset Handling + +For large datasets, the Package Manager implements: + +1. **Pagination**: + - Limits the number of items displayed at once + - Implements virtual scrolling for smooth performance + - Loads additional items as needed + +2. **Progressive Loading**: + - Shows initial results quickly + - Loads additional details asynchronously + - Provides visual feedback during loading + +3. **Background Processing**: + - Performs heavy operations in a web worker + - Keeps the UI responsive during filtering + - Updates results incrementally + +### Search Optimizations + +For efficient searching: + +1. **Debounced Input**: + ```typescript + function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; + } + ``` + +2. **Incremental Matching**: + - Matches characters in sequence + - Prioritizes prefix matches + - Supports fuzzy matching for better results + +3. **Result Highlighting**: + - Highlights matching text portions + - Provides visual feedback on match quality + - Improves user understanding of results + +### Filter Combinations + +For efficient filter combinations: + +1. **Filter Order Optimization**: + - Applies most restrictive filters first + - Reduces dataset size early in the pipeline + - Improves performance for complex filter combinations + +2. **Filter Caching**: + - Caches results for recent filter combinations + - Avoids recomputing the same filters + - Clears cache when underlying data changes + +3. **Progressive Filtering**: + - Shows initial results based on simple filters + - Applies complex filters incrementally + - Provides feedback during filtering process + +## Edge Cases and Error Handling + +The search and filter implementation handles several edge cases: + +### Empty Results + +When no items match the filters: + +```tsx +const NoResults: React.FC<{ + filters: Filters; + clearFilters: () => void; +}> = ({ filters, clearFilters }) => { + return ( +
+ +

No matching packages found

+

+ No packages match your current filters. + {isFilterActive(filters) && ( + <> +
+ + + )} +

+
+ ); +}; +``` + +### Invalid Search Terms + +The system handles invalid search terms: + +- Empty searches show all items +- Special characters are escaped +- Very long search terms are truncated +- Malformed regex patterns are handled safely + +### Filter Conflicts + +When filters conflict: + +- Shows a warning when appropriate +- Provides suggestions to resolve conflicts +- Falls back to reasonable defaults +- Preserves user intent when possible + +## Testing Strategy + +The search and filter functionality includes comprehensive tests: + +### Unit Tests + +```typescript +describe("Search Utils", () => { + describe("containsSearchTerm", () => { + it("should return true for exact matches", () => { + expect(containsSearchTerm("hello world", "hello")).toBe(true); + }); + + it("should be case insensitive", () => { + expect(containsSearchTerm("Hello World", "hello")).toBe(true); + expect(containsSearchTerm("hello world", "WORLD")).toBe(true); + }); + + it("should handle undefined inputs", () => { + expect(containsSearchTerm(undefined, "test")).toBe(false); + expect(containsSearchTerm("test", "")).toBe(false); + }); + }); + + describe("filterItems", () => { + const items = [ + { + name: "Test Package", + description: "A test package", + type: "package", + tags: ["test", "example"] + }, + { + name: "Another Package", + description: "Another test package", + type: "mode", + tags: ["example"] + } + ]; + + it("should filter by type", () => { + const result = filterItems(items, { type: "package" }); + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Test Package"); + }); + + it("should filter by search term", () => { + const result = filterItems(items, { search: "another" }); + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Another Package"); + }); + + it("should filter by tags", () => { + const result = filterItems(items, { tags: ["test"] }); + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Test Package"); + }); + + it("should combine filters", () => { + const result = filterItems(items, { + type: "package", + tags: ["example"] + }); + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Test Package"); + }); + }); +}); +``` + +### Integration Tests + +```typescript +describe("Package Manager Search Integration", () => { + let manager: PackageManagerManager; + let metadataScanner: MetadataScanner; + let templateItems: PackageManagerItem[]; + + beforeAll(async () => { + // Load real data from template + metadataScanner = new MetadataScanner(); + const templatePath = path.resolve(__dirname, "../../../../package-manager-template"); + templateItems = await metadataScanner.scanDirectory(templatePath, "https://example.com"); + }); + + beforeEach(() => { + // Create a real context-like object + const context = { + extensionPath: path.resolve(__dirname, "../../../../"), + globalStorageUri: { fsPath: path.resolve(__dirname, "../../../../mock/settings/path") }, + } as vscode.ExtensionContext; + + // Create real instances + manager = new PackageManagerManager(context); + + // Set up manager with template data + manager["currentItems"] = [...templateItems]; + }); + + it("should find items by name", () => { + const message = { + type: "search", + search: "data platform", + typeFilter: "", + tagFilters: [] + }; + + const result = handlePackageManagerMessages(message, manager); + expect(result.data).toHaveLength(1); + expect(result.data[0].name).toContain("Data Platform"); + }); + + it("should find items with matching subcomponents", () => { + const message = { + type: "search", + search: "validator", + typeFilter: "", + tagFilters: [] + }; + + const result = handlePackageManagerMessages(message, manager); + expect(result.data.length).toBeGreaterThan(0); + + // Check that subcomponents are marked as matches + const hasMatchingSubcomponent = result.data.some(item => + item.items?.some(subItem => subItem.matchInfo?.matched) + ); + expect(hasMatchingSubcomponent).toBe(true); + }); +}); +``` + +--- + +**Previous**: [Data Structures](./03-data-structures.md) | **Next**: [UI Component Design](./05-ui-components.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/05-ui-components.md b/cline_docs/package-manager/implementation/05-ui-components.md new file mode 100644 index 0000000000..fab6499e71 --- /dev/null +++ b/cline_docs/package-manager/implementation/05-ui-components.md @@ -0,0 +1,878 @@ +# UI Component Design + +This document details the design and implementation of the Package Manager's UI components, including their structure, styling, interactions, and accessibility features. + +## PackageManagerItemCard + +The PackageManagerItemCard is the primary component for displaying package information in the UI. + +### Component Structure + +```tsx +export const PackageManagerItemCard: React.FC = ({ + item, + filters, + setFilters, + activeTab, + setActiveTab, +}) => { + // URL validation helper + const isValidUrl = (urlString: string): boolean => { + try { + new URL(urlString); + return true; + } catch (e) { + return false; + } + }; + + // Type label and color helpers + const getTypeLabel = (type: string) => { + switch (type) { + case "mode": + return "Mode"; + case "mcp server": + return "MCP Server"; + case "prompt": + return "Prompt"; + case "package": + return "Package"; + default: + return "Other"; + } + }; + + const getTypeColor = (type: string) => { + switch (type) { + case "mode": + return "bg-blue-600"; + case "mcp server": + return "bg-green-600"; + case "prompt": + return "bg-purple-600"; + case "package": + return "bg-orange-600"; + default: + return "bg-gray-600"; + } + }; + + // URL opening handler + const handleOpenUrl = () => { + const urlToOpen = item.sourceUrl && isValidUrl(item.sourceUrl) ? item.sourceUrl : item.repoUrl; + vscode.postMessage({ + type: "openExternal", + url: urlToOpen, + }); + }; + + // Group items by type + const groupedItems = useMemo(() => { + if (!item.items?.length) { + return null; + } + return groupItemsByType(item.items); + }, [item.items]) as GroupedItems | null; + + return ( +
+ {/* Header section with name, author, and type badge */} +
+
+

{item.name}

+ {item.author &&

{`by ${item.author}`}

} +
+ + {getTypeLabel(item.type)} + +
+ + {/* Description */} +

{item.description}

+ + {/* Tags section */} + {item.tags && item.tags.length > 0 && ( +
+ {item.tags.map((tag) => ( + + ))} +
+ )} + + {/* Footer section with metadata and action button */} +
+
+ {item.version && ( + + + {item.version} + + )} + {item.lastUpdated && ( + + + {new Date(item.lastUpdated).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + })} + + )} +
+ + +
+ + {/* Details section with subcomponents */} + {groupedItems && ( + { + const matchCount = + item.items?.filter( + (subItem) => + (subItem.metadata?.name || "") + .toLowerCase() + .includes(filters.search.toLowerCase()) || + (subItem.metadata?.description || "") + .toLowerCase() + .includes(filters.search.toLowerCase()), + ).length || 0; + return matchCount > 0 + ? `${matchCount} match${matchCount !== 1 ? "es" : ""}` + : undefined; + })() + : undefined + } + defaultExpanded={ + !!filters.search && + (item.items?.some( + (subItem) => + (subItem.metadata?.name || "").toLowerCase().includes(filters.search.toLowerCase()) || + (subItem.metadata?.description || "") + .toLowerCase() + .includes(filters.search.toLowerCase()), + ) || + false) + }> +
+ {Object.entries(groupedItems).map(([type, group]) => ( + + ))} +
+
+ )} +
+ ); +}; +``` + +### Design Considerations + +1. **Visual Hierarchy**: + - Clear distinction between header, content, and footer + - Type badge stands out with color coding + - Important information is emphasized with typography + +2. **Interactive Elements**: + - Tags are clickable for filtering + - External link button for source access + - Expandable details section for subcomponents + +3. **Information Density**: + - Balanced display of essential information + - Optional elements only shown when available + - Expandable section for additional details + +4. **VSCode Integration**: + - Uses VSCode theme variables for colors + - Matches VSCode UI patterns + - Integrates with VSCode messaging system + +## ExpandableSection + +The ExpandableSection component provides a collapsible container for content that doesn't need to be visible at all times. + +### Component Structure + +```tsx +export const ExpandableSection: React.FC = ({ + title, + children, + className, + defaultExpanded = false, + badge, +}) => { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + + return ( +
+ +
+
{children}
+
+
+ ); +}; +``` + +### Design Considerations + +1. **Animation**: + - Smooth height transition for expand/collapse + - Opacity change for better visual feedback + - Chevron icon rotation for state indication + +2. **Accessibility**: + - Proper ARIA attributes for screen readers + - Keyboard navigation support + - Clear visual indication of interactive state + +3. **Flexibility**: + - Accepts any content as children + - Optional badge for additional information + - Customizable through className prop + +4. **State Management**: + - Internal state for expanded/collapsed + - Can be controlled through defaultExpanded prop + - Preserves state during component lifecycle + +## TypeGroup + +The TypeGroup component displays a collection of items of the same type, with special handling for search matches. + +### Component Structure + +```tsx +export const TypeGroup: React.FC = ({ type, items, className, searchTerm }) => { + const getTypeLabel = (type: string) => { + switch (type) { + case "mode": + return "Modes"; + case "mcp server": + return "MCP Servers"; + case "prompt": + return "Prompts"; + case "package": + return "Packages"; + default: + return `${type.charAt(0).toUpperCase()}${type.slice(1)}s`; + } + }; + + if (!items?.length) { + return null; + } + + // Check if an item matches the search term + const itemMatchesSearch = (item: { name: string; description?: string }) => { + if (!searchTerm) return false; + const term = searchTerm.toLowerCase(); + return item.name.toLowerCase().includes(term) || (item.description || "").toLowerCase().includes(term); + }; + + return ( +
+

{getTypeLabel(type)}

+
    + {items.map((item, index) => { + const matches = itemMatchesSearch(item); + return ( +
  1. + + {item.name} + + {item.description && ( + - {item.description} + )} + {matches && ( + + match + + )} +
  2. + ); + })} +
+
+ ); +}; +``` + +### Design Considerations + +1. **List Presentation**: + - Ordered list with automatic numbering + - Clear type heading for context + - Consistent spacing for readability + +2. **Search Match Highlighting**: + - Visual distinction for matching items + - "match" badge for quick identification + - Color change for matched text + +3. **Information Display**: + - Name and description clearly separated + - Tooltip shows path information on hover + - Truncation for very long descriptions + +4. **Empty State Handling**: + - Returns null when no items are present + - Avoids rendering empty containers + - Prevents unnecessary UI elements + +## Filter Components + +The Package Manager includes several components for filtering and searching. + +### SearchInput + +```tsx +const SearchInput: React.FC<{ + value: string; + onChange: (value: string) => void; +}> = ({ value, onChange }) => { + // Debounce search input to avoid excessive filtering + const debouncedOnChange = useDebounce(onChange, 300); + + return ( +
+ + debouncedOnChange(e.target.value)} + placeholder="Search packages..." + className="search-input" + aria-label="Search packages" + /> + {value && ( + + )} +
+ ); +}; +``` + +### TypeFilterGroup + +```tsx +const TypeFilterGroup: React.FC<{ + selectedType: string; + onChange: (type: string) => void; + availableTypes: string[]; +}> = ({ selectedType, onChange, availableTypes }) => { + return ( +
+

Filter by Type

+
+ + + {availableTypes.map((type) => ( + + ))} +
+
+ ); +}; +``` + +### TagFilterGroup + +```tsx +const TagFilterGroup: React.FC<{ + selectedTags: string[]; + onChange: (tags: string[]) => void; + availableTags: string[]; +}> = ({ selectedTags, onChange, availableTags }) => { + const toggleTag = (tag: string) => { + if (selectedTags.includes(tag)) { + onChange(selectedTags.filter(t => t !== tag)); + } else { + onChange([...selectedTags, tag]); + } + }; + + return ( +
+

Filter by Tags

+
+ {availableTags.map((tag) => ( + + ))} +
+
+ ); +}; +``` + +## Styling Approach + +The Package Manager UI uses a combination of Tailwind CSS and VSCode theme variables for styling. + +### VSCode Theme Integration + +The components use VSCode theme variables to ensure they match the user's selected theme: + +```css +/* Example of VSCode theme variable usage */ +.package-card { + background-color: var(--vscode-panel-background); + border-color: var(--vscode-panel-border); + color: var(--vscode-foreground); +} + +.package-description { + color: var(--vscode-descriptionForeground); +} + +.package-link { + color: var(--vscode-textLink-foreground); +} + +.package-link:hover { + color: var(--vscode-textLink-activeForeground); +} +``` + +### Tailwind CSS Usage + +Tailwind CSS is used for utility-based styling: + +```tsx +// Example of Tailwind CSS usage +
+

{item.name}

+ + {getTypeLabel(item.type)} + +
+``` + +### Custom Utility Functions + +The UI uses utility functions for class name composition: + +```typescript +// cn utility for conditional class names +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +``` + +## Responsive Design + +The Package Manager UI is designed to work across different viewport sizes: + +### Layout Adjustments + +```tsx +// Example of responsive layout +
+ {items.map(item => ( + + ))} +
+``` + +### Mobile Considerations + +For smaller screens: + +1. **Stacked Layout**: + - Cards stack vertically on small screens + - Filter panel collapses to a dropdown + - Full-width elements for better touch targets + +2. **Touch Optimization**: + - Larger touch targets for mobile users + - Swipe gestures for common actions + - Simplified interactions for touch devices + +3. **Content Prioritization**: + - Critical information shown first + - Less important details hidden behind expandable sections + - Reduced information density on small screens + +## Accessibility Features + +The Package Manager UI includes several accessibility features: + +### Keyboard Navigation + +```tsx +// Example of keyboard navigation support + +``` + +### Screen Reader Support + +```tsx +// Example of screen reader support +
+ + +
+``` + +### Focus Management + +```tsx +// Example of focus management +const buttonRef = useRef(null); + +useEffect(() => { + if (isOpen && buttonRef.current) { + buttonRef.current.focus(); + } +}, [isOpen]); + +return ( + +); +``` + +### Color Contrast + +The UI ensures sufficient color contrast for all text: + +- Text uses VSCode theme variables that maintain proper contrast +- Interactive elements have clear focus states +- Color is not the only means of conveying information + +## Animation and Transitions + +The Package Manager UI uses subtle animations to enhance the user experience: + +### Expand/Collapse Animation + +```tsx +// Example of expand/collapse animation +
+ {children} +
+``` + +### Hover Effects + +```tsx +// Example of hover effects + +``` + +### Loading States + +```tsx +// Example of loading state animation +
+
+ Loading packages... +
+``` + +## Error Handling in UI + +The Package Manager UI includes graceful error handling: + +### Error States + +```tsx +// Example of error state display +const ErrorDisplay: React.FC<{ error: string; retry: () => void }> = ({ error, retry }) => { + return ( +
+
+ +

Error loading packages

+
+

{error}

+ +
+ ); +}; +``` + +### Empty States + +```tsx +// Example of empty state display +const EmptyState: React.FC<{ message: string }> = ({ message }) => { + return ( +
+
+

{message}

+
+ ); +}; +``` + +### Loading States + +```tsx +// Example of loading state with skeleton +const PackageCardSkeleton: React.FC = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; +``` + +## Component Testing + +The Package Manager UI components include comprehensive tests: + +### Unit Tests + +```typescript +// Example of component unit test +describe("PackageManagerItemCard", () => { + const mockItem: PackageManagerItem = { + name: "Test Package", + description: "A test package", + type: "package", + url: "https://example.com", + repoUrl: "https://github.com/example/repo", + tags: ["test", "example"], + version: "1.0.0", + lastUpdated: "2025-04-01" + }; + + const mockFilters = { type: "", search: "", tags: [] }; + const mockSetFilters = jest.fn(); + const mockSetActiveTab = jest.fn(); + + it("renders correctly", () => { + render( + + ); + + expect(screen.getByText("Test Package")).toBeInTheDocument(); + expect(screen.getByText("A test package")).toBeInTheDocument(); + expect(screen.getByText("Package")).toBeInTheDocument(); + }); + + it("handles tag clicks", () => { + render( + + ); + + fireEvent.click(screen.getByText("test")); + + expect(mockSetFilters).toHaveBeenCalledWith({ + type: "", + search: "", + tags: ["test"] + }); + }); +}); +``` + +### Snapshot Tests + +```typescript +// Example of snapshot test +it("matches snapshot", () => { + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); +}); +``` + +### Accessibility Tests + +```typescript +// Example of accessibility test +it("meets accessibility requirements", async () => { + const { container } = render( + + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); +``` + +--- + +**Previous**: [Search and Filter Implementation](./04-search-and-filter.md) | **Next**: [Testing Strategy](./06-testing-strategy.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/06-testing-strategy.md b/cline_docs/package-manager/implementation/06-testing-strategy.md new file mode 100644 index 0000000000..251be86a9c --- /dev/null +++ b/cline_docs/package-manager/implementation/06-testing-strategy.md @@ -0,0 +1,885 @@ +# Testing Strategy + +This document outlines the comprehensive testing strategy for the Package Manager, including unit tests, integration tests, and test data management. + +## Testing Philosophy + +The Package Manager follows a multi-layered testing approach to ensure reliability and maintainability: + +1. **Unit Testing**: Testing individual components in isolation +2. **Integration Testing**: Testing interactions between components +3. **End-to-End Testing**: Testing complete user workflows +4. **Test-Driven Development**: Writing tests before implementation when appropriate +5. **Continuous Testing**: Running tests automatically on code changes + +## Unit Tests + +Unit tests focus on testing individual functions, classes, and components in isolation. + +### Backend Unit Tests + +Backend unit tests verify the functionality of core services and utilities: + +#### MetadataScanner Tests + +```typescript +describe("MetadataScanner", () => { + let scanner: MetadataScanner; + + beforeEach(() => { + scanner = new MetadataScanner(); + }); + + describe("parseMetadataFile", () => { + it("should parse valid YAML metadata", async () => { + // Mock file system + jest.spyOn(fs, "readFile").mockImplementation((path, options, callback) => { + callback(null, Buffer.from(` + name: "Test Package" + description: "A test package" + version: "1.0.0" + type: "package" + `)); + }); + + const result = await scanner["parseMetadataFile"]("test/path/metadata.en.yml"); + + expect(result).toEqual({ + name: "Test Package", + description: "A test package", + version: "1.0.0", + type: "package" + }); + }); + + it("should handle invalid YAML", async () => { + // Mock file system with invalid YAML + jest.spyOn(fs, "readFile").mockImplementation((path, options, callback) => { + callback(null, Buffer.from(` + name: "Invalid YAML + description: Missing quote + `)); + }); + + await expect(scanner["parseMetadataFile"]("test/path/metadata.en.yml")) + .rejects.toThrow(); + }); + }); + + describe("scanDirectory", () => { + // Tests for directory scanning + }); +}); +``` + +#### PackageManagerManager Tests + +```typescript +describe("PackageManagerManager", () => { + let manager: PackageManagerManager; + let mockContext: vscode.ExtensionContext; + + beforeEach(() => { + // Create mock context + mockContext = { + extensionPath: "/test/path", + globalStorageUri: { fsPath: "/test/storage" }, + globalState: { + get: jest.fn().mockImplementation((key, defaultValue) => defaultValue), + update: jest.fn().mockResolvedValue(undefined) + } + } as unknown as vscode.ExtensionContext; + + manager = new PackageManagerManager(mockContext); + }); + + describe("filterItems", () => { + it("should filter by type", () => { + // Set up test data + manager["currentItems"] = [ + { name: "Item 1", type: "mode", description: "Test item 1" }, + { name: "Item 2", type: "package", description: "Test item 2" } + ] as PackageManagerItem[]; + + const result = manager.filterItems({ type: "mode" }); + + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Item 1"); + }); + + it("should filter by search term", () => { + // Set up test data + manager["currentItems"] = [ + { name: "Alpha Item", type: "mode", description: "Test item" }, + { name: "Beta Item", type: "package", description: "Another test" } + ] as PackageManagerItem[]; + + const result = manager.filterItems({ search: "alpha" }); + + expect(result).toHaveLength(1); + expect(result[0].name).toBe("Alpha Item"); + }); + + // More filter tests... + }); + + describe("addSource", () => { + // Tests for adding sources + }); +}); +``` + +#### Search Utilities Tests + +```typescript +describe("searchUtils", () => { + describe("containsSearchTerm", () => { + it("should return true for exact matches", () => { + expect(containsSearchTerm("hello world", "hello")).toBe(true); + }); + + it("should be case insensitive", () => { + expect(containsSearchTerm("Hello World", "hello")).toBe(true); + expect(containsSearchTerm("hello world", "WORLD")).toBe(true); + }); + + it("should handle undefined inputs", () => { + expect(containsSearchTerm(undefined, "test")).toBe(false); + expect(containsSearchTerm("test", "")).toBe(false); + }); + }); + + describe("itemMatchesSearch", () => { + it("should match on name", () => { + const item = { + name: "Test Item", + description: "Description" + }; + + expect(itemMatchesSearch(item, "test")).toEqual({ + matched: true, + matchReason: { + nameMatch: true, + descriptionMatch: false + } + }); + }); + + // More search matching tests... + }); +}); +``` + +### Frontend Unit Tests + +Frontend unit tests verify the functionality of UI components: + +#### PackageManagerItemCard Tests + +```typescript +describe("PackageManagerItemCard", () => { + const mockItem: PackageManagerItem = { + name: "Test Package", + description: "A test package", + type: "package", + url: "https://example.com", + repoUrl: "https://github.com/example/repo", + tags: ["test", "example"], + version: "1.0.0", + lastUpdated: "2025-04-01" + }; + + const mockFilters = { type: "", search: "", tags: [] }; + const mockSetFilters = jest.fn(); + const mockSetActiveTab = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders correctly", () => { + render( + + ); + + expect(screen.getByText("Test Package")).toBeInTheDocument(); + expect(screen.getByText("A test package")).toBeInTheDocument(); + expect(screen.getByText("Package")).toBeInTheDocument(); + }); + + it("handles tag clicks", () => { + render( + + ); + + fireEvent.click(screen.getByText("test")); + + expect(mockSetFilters).toHaveBeenCalledWith({ + type: "", + search: "", + tags: ["test"] + }); + }); + + // More component tests... +}); +``` + +#### ExpandableSection Tests + +```typescript +describe("ExpandableSection", () => { + it("renders collapsed by default", () => { + render( + +
Test Content
+
+ ); + + expect(screen.getByText("Test Section")).toBeInTheDocument(); + expect(screen.queryByText("Test Content")).not.toBeVisible(); + }); + + it("expands when clicked", () => { + render( + +
Test Content
+
+ ); + + fireEvent.click(screen.getByText("Test Section")); + + expect(screen.getByText("Test Content")).toBeVisible(); + }); + + it("can be expanded by default", () => { + render( + +
Test Content
+
+ ); + + expect(screen.getByText("Test Content")).toBeVisible(); + }); + + // More component tests... +}); +``` + +#### TypeGroup Tests + +```typescript +describe("TypeGroup", () => { + const mockItems = [ + { name: "Item 1", description: "Description 1" }, + { name: "Item 2", description: "Description 2" } + ]; + + it("renders type heading and items", () => { + render(); + + expect(screen.getByText("Modes")).toBeInTheDocument(); + expect(screen.getByText("Item 1")).toBeInTheDocument(); + expect(screen.getByText("Item 2")).toBeInTheDocument(); + }); + + it("highlights items matching search term", () => { + render(); + + const item1 = screen.getByText("Item 1"); + const item2 = screen.getByText("Item 2"); + + expect(item1.className).toContain("text-vscode-textLink"); + expect(item2.className).not.toContain("text-vscode-textLink"); + expect(screen.getByText("match")).toBeInTheDocument(); + }); + + // More component tests... +}); +``` + +## Integration Tests + +Integration tests verify that different components work together correctly. + +### Backend Integration Tests + +```typescript +describe("Package Manager Integration", () => { + let manager: PackageManagerManager; + let metadataScanner: MetadataScanner; + let templateItems: PackageManagerItem[]; + + beforeAll(async () => { + // Load real data from template + metadataScanner = new MetadataScanner(); + const templatePath = path.resolve(__dirname, "../../../../package-manager-template"); + templateItems = await metadataScanner.scanDirectory(templatePath, "https://example.com"); + }); + + beforeEach(() => { + // Create a real context-like object + const context = { + extensionPath: path.resolve(__dirname, "../../../../"), + globalStorageUri: { fsPath: path.resolve(__dirname, "../../../../mock/settings/path") }, + } as vscode.ExtensionContext; + + // Create real instances + manager = new PackageManagerManager(context); + + // Set up manager with template data + manager["currentItems"] = [...templateItems]; + }); + + describe("Message Handler Integration", () => { + it("should handle search messages", async () => { + const message = { + type: "search", + search: "data platform", + typeFilter: "", + tagFilters: [] + }; + + const result = await handlePackageManagerMessages(message, manager); + + expect(result.type).toBe("searchResults"); + expect(result.data).toHaveLength(1); + expect(result.data[0].name).toContain("Data Platform"); + }); + + it("should handle type filter messages", async () => { + const message = { + type: "search", + search: "", + typeFilter: "mode", + tagFilters: [] + }; + + const result = await handlePackageManagerMessages(message, manager); + + expect(result.type).toBe("searchResults"); + expect(result.data.every(item => item.type === "mode")).toBe(true); + }); + + // More message handler tests... + }); + + describe("End-to-End Flow", () => { + it("should find items with matching subcomponents", async () => { + const message = { + type: "search", + search: "validator", + typeFilter: "", + tagFilters: [] + }; + + const result = await handlePackageManagerMessages(message, manager); + + expect(result.data.length).toBeGreaterThan(0); + + // Check that subcomponents are marked as matches + const hasMatchingSubcomponent = result.data.some(item => + item.items?.some(subItem => subItem.matchInfo?.matched) + ); + expect(hasMatchingSubcomponent).toBe(true); + }); + + // More end-to-end flow tests... + }); +}); +``` + +### Frontend Integration Tests + +```typescript +describe("Package Manager UI Integration", () => { + const mockItems: PackageManagerItem[] = [ + { + name: "Test Package", + description: "A test package", + type: "package", + url: "https://example.com", + repoUrl: "https://github.com/example/repo", + tags: ["test", "example"], + items: [ + { + type: "mode", + path: "/test/path", + metadata: { + name: "Test Mode", + description: "A test mode", + type: "mode" + } + } + ] + }, + { + name: "Another Package", + description: "Another test package", + type: "mode", + url: "https://example.com", + repoUrl: "https://github.com/example/repo", + tags: ["example"] + } + ]; + + beforeEach(() => { + // Mock VSCode API + (vscode.postMessage as jest.Mock).mockClear(); + }); + + it("should filter items when search is entered", async () => { + render(); + + // Both packages should be visible initially + expect(screen.getByText("Test Package")).toBeInTheDocument(); + expect(screen.getByText("Another Package")).toBeInTheDocument(); + + // Enter search term + const searchInput = screen.getByPlaceholderText("Search packages..."); + fireEvent.change(searchInput, { target: { value: "another" } }); + + // Wait for debounce + await waitFor(() => { + expect(screen.queryByText("Test Package")).not.toBeInTheDocument(); + expect(screen.getByText("Another Package")).toBeInTheDocument(); + }); + }); + + it("should expand details when search matches subcomponents", async () => { + render(); + + // Enter search term that matches a subcomponent + const searchInput = screen.getByPlaceholderText("Search packages..."); + fireEvent.change(searchInput, { target: { value: "test mode" } }); + + // Wait for debounce and expansion + await waitFor(() => { + expect(screen.getByText("Test Mode")).toBeInTheDocument(); + expect(screen.getByText("A test mode")).toBeInTheDocument(); + }); + + // Check that the match is highlighted + const modeElement = screen.getByText("Test Mode"); + expect(modeElement.className).toContain("text-vscode-textLink"); + }); + + // More UI integration tests... +}); +``` + +## Test Data Management + +The Package Manager uses several approaches to manage test data: + +### Mock Data + +Mock data is used for simple unit tests: + +```typescript +const mockItems: PackageManagerItem[] = [ + { + name: "Test Package", + description: "A test package", + type: "package", + url: "https://example.com", + repoUrl: "https://github.com/example/repo", + tags: ["test", "example"], + version: "1.0.0" + }, + // More mock items... +]; +``` + +### Test Fixtures + +Test fixtures provide more complex data structures: + +```typescript +// fixtures/metadata.ts +export const metadataFixtures = { + basic: { + name: "Basic Package", + description: "A basic package for testing", + version: "1.0.0", + type: "package" + }, + + withTags: { + name: "Tagged Package", + description: "A package with tags", + version: "1.0.0", + type: "package", + tags: ["test", "fixture", "example"] + }, + + withSubcomponents: { + name: "Complex Package", + description: "A package with subcomponents", + version: "1.0.0", + type: "package", + items: [ + { + type: "mode", + path: "/test/path/mode", + metadata: { + name: "Test Mode", + description: "A test mode", + type: "mode" + } + }, + { + type: "mcp server", + path: "/test/path/server", + metadata: { + name: "Test Server", + description: "A test server", + type: "mcp server" + } + } + ] + } +}; +``` + +### Template Data + +Real template data is used for integration tests: + +```typescript +beforeAll(async () => { + // Load real data from template + metadataScanner = new MetadataScanner(); + const templatePath = path.resolve(__dirname, "../../../../package-manager-template"); + templateItems = await metadataScanner.scanDirectory(templatePath, "https://example.com"); +}); +``` + +### Test Data Generators + +Generators create varied test data: + +```typescript +// Test data generator +function generatePackageItems(count: number): PackageManagerItem[] { + const types: ComponentType[] = ["mode", "mcp server", "package", "prompt"]; + const tags = ["test", "example", "data", "ui", "server", "client"]; + + return Array.from({ length: count }, (_, i) => { + const type = types[i % types.length]; + const randomTags = tags + .filter(() => Math.random() > 0.5) + .slice(0, Math.floor(Math.random() * 4)); + + return { + name: `Test ${type} ${i + 1}`, + description: `This is a test ${type} for testing purposes`, + type, + url: `https://example.com/${type}/${i + 1}`, + repoUrl: "https://github.com/example/repo", + tags: randomTags.length ? randomTags : undefined, + version: "1.0.0", + lastUpdated: new Date().toISOString(), + items: type === "package" ? generateSubcomponents(Math.floor(Math.random() * 5) + 1) : undefined + }; + }); +} + +function generateSubcomponents(count: number): PackageManagerItem["items"] { + const types: ComponentType[] = ["mode", "mcp server", "prompt"]; + + return Array.from({ length: count }, (_, i) => { + const type = types[i % types.length]; + + return { + type, + path: `/test/path/${type}/${i + 1}`, + metadata: { + name: `Test ${type} ${i + 1}`, + description: `This is a test ${type} subcomponent`, + type + } + }; + }); +} +``` + +## Test Organization + +The Package Manager tests are organized by functionality rather than by file structure: + +### Consolidated Test Files + +``` +src/services/package-manager/__tests__/ +├── PackageManager.consolidated.test.ts # Combined tests +├── searchUtils.test.ts # Search utility tests +└── PackageSubcomponents.test.ts # Subcomponent tests +``` + +### Test Structure + +Tests are organized into logical groups: + +```typescript +describe("Package Manager", () => { + // Shared setup + + describe("Direct Filtering", () => { + // Tests for filtering functionality + }); + + describe("Message Handler Integration", () => { + // Tests for message handling + }); + + describe("Sorting", () => { + // Tests for sorting functionality + }); +}); +``` + +## Test Coverage + +The Package Manager maintains high test coverage: + +### Coverage Goals + +- **Backend Logic**: 90%+ coverage +- **UI Components**: 80%+ coverage +- **Integration Points**: 85%+ coverage + +### Coverage Reporting + +```typescript +// jest.config.js +module.exports = { + // ...other config + collectCoverage: true, + coverageReporters: ["text", "lcov", "html"], + coverageThreshold: { + global: { + branches: 80, + functions: 85, + lines: 85, + statements: 85 + }, + "src/services/package-manager/*.ts": { + branches: 90, + functions: 90, + lines: 90, + statements: 90 + } + } +}; +``` + +### Critical Path Testing + +Critical paths have additional test coverage: + +1. **Search and Filter**: Comprehensive tests for all filter combinations +2. **Message Handling**: Tests for all message types and error conditions +3. **UI Interactions**: Tests for all user interaction flows + +## Test Performance + +The Package Manager tests are optimized for performance: + +### Fast Unit Tests + +```typescript +// Fast unit tests with minimal dependencies +describe("containsSearchTerm", () => { + it("should return true for exact matches", () => { + expect(containsSearchTerm("hello world", "hello")).toBe(true); + }); + + // More tests... +}); +``` + +### Optimized Integration Tests + +```typescript +// Optimized integration tests +describe("Package Manager Integration", () => { + // Load template data once for all tests + beforeAll(async () => { + templateItems = await metadataScanner.scanDirectory(templatePath); + }); + + // Create fresh manager for each test + beforeEach(() => { + manager = new PackageManagerManager(mockContext); + manager["currentItems"] = [...templateItems]; + }); + + // Tests... +}); +``` + +### Parallel Test Execution + +```typescript +// jest.config.js +module.exports = { + // ...other config + maxWorkers: "50%", // Use 50% of available cores + maxConcurrency: 5 // Run up to 5 tests concurrently +}; +``` + +## Continuous Integration + +The Package Manager tests are integrated into the CI/CD pipeline: + +### GitHub Actions Workflow + +```yaml +# .github/workflows/test.yml +name: Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Upload coverage + uses: codecov/codecov-action@v2 + with: + file: ./coverage/lcov.info +``` + +### Pre-commit Hooks + +```json +// package.json +{ + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "jest --findRelatedTests" + ] + } +} +``` + +## Test Debugging + +The Package Manager includes tools for debugging tests: + +### Debug Logging + +```typescript +// Debug logging in tests +describe("Complex integration test", () => { + it("should handle complex search", async () => { + // Enable debug logging for this test + const originalDebug = process.env.DEBUG; + process.env.DEBUG = "package-manager:*"; + + // Test logic... + + // Restore debug setting + process.env.DEBUG = originalDebug; + }); +}); +``` + +### Visual Debugging + +```typescript +// Visual debugging for UI tests +describe("UI component test", () => { + it("should render correctly", async () => { + const { container } = render(); + + // Save screenshot for visual debugging + if (process.env.SAVE_SCREENSHOTS) { + const screenshot = await page.screenshot(); + fs.writeFileSync("./screenshots/item-card.png", screenshot); + } + + // Test assertions... + }); +}); +``` + +## Test Documentation + +The Package Manager tests include comprehensive documentation: + +### Test Comments + +```typescript +/** + * Tests the search functionality with various edge cases + * + * Edge cases covered: + * - Empty search term + * - Case sensitivity + * - Special characters + * - Very long search terms + * - Matching in subcomponents + */ +describe("Search functionality", () => { + // Tests... +}); +``` + +### Test Scenarios + +```typescript +describe("Package filtering", () => { + /** + * Scenario: User filters by type and search term + * Given: A list of packages of different types + * When: The user selects a type filter and enters a search term + * Then: Only packages of the selected type containing the search term should be shown + */ + it("should combine type and search filters", () => { + // Test implementation... + }); +}); +``` + +--- + +**Previous**: [UI Component Design](./05-ui-components.md) | **Next**: [Extending the Package Manager](./07-extending.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/07-extending.md b/cline_docs/package-manager/implementation/07-extending.md new file mode 100644 index 0000000000..939ceac780 --- /dev/null +++ b/cline_docs/package-manager/implementation/07-extending.md @@ -0,0 +1,956 @@ +# Extending the Package Manager + +This document provides guidance on extending the Package Manager with new features, component types, and customizations. + +## Adding New Component Types + +The Package Manager is designed to be extensible, allowing for the addition of new component types beyond the default ones (mode, mcp server, prompt, package). + +### Extending the ComponentType + +To add a new component type: + +1. **Update the ComponentType Type**: + +```typescript +/** + * Supported component types + */ +export type ComponentType = "mode" | "prompt" | "package" | "mcp server" | "your-new-type"; +``` + +2. **Update Type Label Functions**: + +```typescript +const getTypeLabel = (type: string) => { + switch (type) { + case "mode": + return "Mode"; + case "mcp server": + return "MCP Server"; + case "prompt": + return "Prompt"; + case "package": + return "Package"; + case "your-new-type": + return "Your New Type"; + default: + return "Other"; + } +}; +``` + +3. **Update Type Color Functions**: + +```typescript +const getTypeColor = (type: string) => { + switch (type) { + case "mode": + return "bg-blue-600"; + case "mcp server": + return "bg-green-600"; + case "prompt": + return "bg-purple-600"; + case "package": + return "bg-orange-600"; + case "your-new-type": + return "bg-yellow-600"; // Choose a distinctive color + default: + return "bg-gray-600"; + } +}; +``` + +4. **Update Type Group Labels**: + +```typescript +const getTypeGroupLabel = (type: string) => { + switch (type) { + case "mode": + return "Modes"; + case "mcp server": + return "MCP Servers"; + case "prompt": + return "Prompts"; + case "package": + return "Packages"; + case "your-new-type": + return "Your New Types"; + default: + return `${type.charAt(0).toUpperCase()}${type.slice(1)}s`; + } +}; +``` + +### Directory Structure for New Types + +When adding a new component type, follow this directory structure in your package source repository: + +``` +repository-root/ +├── metadata.en.yml +├── your-new-type/ # Directory for your new component type +│ ├── component-1/ +│ │ └── metadata.en.yml +│ └── component-2/ +│ └── metadata.en.yml +└── ... +``` + +### Metadata for New Types + +The metadata for your new component type should follow the standard format: + +```yaml +name: "Your Component Name" +description: "Description of your component" +version: "1.0.0" +type: "your-new-type" +tags: + - relevant-tag-1 + - relevant-tag-2 +``` + +### UI Considerations for New Types + +When adding a new component type, consider these UI aspects: + +1. **Type Filtering**: + - Add your new type to the type filter options + - Ensure proper labeling and styling + +2. **Type-Specific Rendering**: + - Consider if your type needs special rendering in the UI + - Add any type-specific UI components or styles + +3. **Type Icons**: + - Choose an appropriate icon for your type + - Add it to the icon mapping + +```typescript +const getTypeIcon = (type: string) => { + switch (type) { + case "mode": + return "codicon-person"; + case "mcp server": + return "codicon-server"; + case "prompt": + return "codicon-comment"; + case "package": + return "codicon-package"; + case "your-new-type": + return "codicon-your-icon"; // Choose an appropriate icon + default: + return "codicon-symbol-misc"; + } +}; +``` + +## Creating Custom Templates + +You can create custom templates to provide a starting point for users creating new components. + +### Template Structure + +A custom template should follow this structure: + +``` +custom-template/ +├── metadata.en.yml +├── README.md +└── [component-specific files] +``` + +### Template Metadata + +The template metadata should include: + +```yaml +name: "Your Template Name" +description: "Description of your template" +version: "1.0.0" +type: "your-component-type" +template: true +templateFor: "your-component-type" +``` + +### Template Registration + +Register your template with the Package Manager: + +```typescript +// In your extension code +const registerTemplates = (context: vscode.ExtensionContext) => { + const templatePath = path.join(context.extensionPath, "templates", "your-template"); + packageManager.registerTemplate(templatePath); +}; +``` + +### Template Usage + +Users can create new components from your template: + +```typescript +// In the UI +const createFromTemplate = (templateName: string) => { + vscode.postMessage({ + type: "createFromTemplate", + templateName + }); +}; +``` + +## Implementing New Features + +The Package Manager is designed to be extended with new features. Here's how to implement common types of features: + +### Adding a New Filter Type + +To add a new filter type (beyond type, search, and tags): + +1. **Update the Filters Interface**: + +```typescript +interface Filters { + type: string; + search: string; + tags: string[]; + yourNewFilter: string; // Add your new filter +} +``` + +2. **Update the Filter Function**: + +```typescript +export function filterItems( + items: PackageManagerItem[], + filters: { + type?: string; + search?: string; + tags?: string[]; + yourNewFilter?: string; // Add your new filter + } +): PackageManagerItem[] { + // Existing filter logic... + + // Add your new filter logic + if (filters.yourNewFilter) { + result = result.filter(item => { + // Your filter implementation + return yourFilterLogic(item, filters.yourNewFilter); + }); + } + + return result; +} +``` + +3. **Add UI Controls**: + +```tsx +const YourNewFilterControl: React.FC<{ + value: string; + onChange: (value: string) => void; +}> = ({ value, onChange }) => { + return ( +
+

Your New Filter

+ {/* Your filter UI controls */} +
+ ); +}; +``` + +4. **Integrate with the Main UI**: + +```tsx + + + + + + +``` + +### Adding a New View Mode + +To add a new view mode (beyond the card view): + +1. **Add a View Mode State**: + +```typescript +type ViewMode = "card" | "list" | "yourNewView"; + +const [viewMode, setViewMode] = useState("card"); +``` + +2. **Create the View Component**: + +```tsx +const YourNewView: React.FC<{ + items: PackageManagerItem[]; + filters: Filters; + setFilters: (filters: Filters) => void; +}> = ({ items, filters, setFilters }) => { + return ( +
+ {/* Your view implementation */} +
+ ); +}; +``` + +3. **Add View Switching Controls**: + +```tsx +const ViewModeSelector: React.FC<{ + viewMode: ViewMode; + setViewMode: (mode: ViewMode) => void; +}> = ({ viewMode, setViewMode }) => { + return ( +
+ + + +
+ ); +}; +``` + +4. **Integrate with the Main UI**: + +```tsx +
+
+ + {/* Other toolbar items */} +
+ +
+ {viewMode === "card" && ( + + )} + {viewMode === "list" && ( + + )} + {viewMode === "yourNewView" && ( + + )} +
+
+``` + +### Adding Custom Actions + +To add custom actions for package items: + +1. **Create an Action Handler**: + +```typescript +const handleCustomAction = (item: PackageManagerItem) => { + vscode.postMessage({ + type: "customAction", + item: item.name, + itemType: item.type + }); +}; +``` + +2. **Add Action Button to the UI**: + +```tsx + +``` + +3. **Handle the Action in the Message Handler**: + +```typescript +case "customAction": + // Handle the custom action + const { item, itemType } = message; + // Your custom action implementation + return { + type: "customActionResult", + success: true, + data: { /* result data */ } + }; +``` + +## Customizing the UI + +The Package Manager UI can be customized in several ways: + +### Custom Styling + +To customize the styling: + +1. **Add Custom CSS Variables**: + +```css +/* In your CSS file */ +:root { + --package-card-bg: var(--vscode-panel-background); + --package-card-border: var(--vscode-panel-border); + --package-card-hover: var(--vscode-list-hoverBackground); + --your-custom-variable: #your-color; +} +``` + +2. **Use Custom Classes**: + +```tsx +
+
+ {/* Your custom UI */} +
+
+``` + +3. **Add Custom Themes**: + +```typescript +type Theme = "default" | "compact" | "detailed" | "yourCustomTheme"; + +const [theme, setTheme] = useState("default"); + +// Theme-specific styles +const getThemeClasses = (theme: Theme) => { + switch (theme) { + case "compact": + return "compact-theme"; + case "detailed": + return "detailed-theme"; + case "yourCustomTheme": + return "your-custom-theme"; + default: + return "default-theme"; + } +}; +``` + +### Custom Components + +To replace or extend existing components: + +1. **Create a Custom Component**: + +```tsx +const CustomPackageCard: React.FC = (props) => { + // Your custom implementation + return ( +
+ {/* Your custom UI */} +

{props.item.name}

+ {/* Additional custom elements */} +
+ {/* Custom footer content */} +
+
+ ); +}; +``` + +2. **Use Component Injection**: + +```tsx +interface ComponentOverrides { + PackageCard?: React.ComponentType; + ExpandableSection?: React.ComponentType; + TypeGroup?: React.ComponentType; +} + +const PackageManagerView: React.FC<{ + initialItems: PackageManagerItem[]; + componentOverrides?: ComponentOverrides; +}> = ({ initialItems, componentOverrides = {} }) => { + // Component selection logic + const PackageCard = componentOverrides.PackageCard || PackageManagerItemCard; + + return ( +
+ {items.map(item => ( + + ))} +
+ ); +}; +``` + +### Custom Layouts + +To implement custom layouts: + +1. **Create a Layout Component**: + +```tsx +const CustomLayout: React.FC<{ + sidebar: React.ReactNode; + content: React.ReactNode; + footer?: React.ReactNode; +}> = ({ sidebar, content, footer }) => { + return ( +
+
{sidebar}
+
{content}
+ {footer &&
{footer}
} +
+ ); +}; +``` + +2. **Use the Layout in the Main UI**: + +```tsx + + } + content={ +
+ {filteredItems.map(item => ( + + ))} +
+ } + footer={ +
+ {`Showing ${filteredItems.length} of ${items.length} packages`} +
+ } +/> +``` + +## Extending Backend Functionality + +The Package Manager backend can be extended with new functionality: + +### Custom Source Providers + +To add support for new source types: + +1. **Create a Source Provider Interface**: + +```typescript +interface SourceProvider { + type: string; + canHandle(url: string): boolean; + fetchItems(url: string): Promise; +} +``` + +2. **Implement a Custom Provider**: + +```typescript +class CustomSourceProvider implements SourceProvider { + type = "custom"; + + canHandle(url: string): boolean { + return url.startsWith("custom://"); + } + + async fetchItems(url: string): Promise { + // Your custom implementation + // Fetch items from your custom source + return items; + } +} +``` + +3. **Register the Provider**: + +```typescript +// In your extension code +const registerSourceProviders = (packageManager: PackageManagerManager) => { + packageManager.registerSourceProvider(new CustomSourceProvider()); +}; +``` + +### Custom Metadata Processors + +To add support for custom metadata formats: + +1. **Create a Metadata Processor Interface**: + +```typescript +interface MetadataProcessor { + canProcess(filePath: string): boolean; + process(filePath: string, content: string): Promise; +} +``` + +2. **Implement a Custom Processor**: + +```typescript +class CustomMetadataProcessor implements MetadataProcessor { + canProcess(filePath: string): boolean { + return filePath.endsWith(".custom"); + } + + async process(filePath: string, content: string): Promise { + // Your custom processing logic + return processedMetadata; + } +} +``` + +3. **Register the Processor**: + +```typescript +// In your extension code +const registerMetadataProcessors = (metadataScanner: MetadataScanner) => { + metadataScanner.registerProcessor(new CustomMetadataProcessor()); +}; +``` + +### Custom Message Handlers + +To add support for custom messages: + +1. **Extend the Message Handler**: + +```typescript +// In your extension code +const extendMessageHandler = () => { + const originalHandler = handlePackageManagerMessages; + + return async (message: any, packageManager: PackageManagerManager) => { + // Handle custom messages + if (message.type === "yourCustomMessage") { + // Your custom message handling + return { + type: "yourCustomResponse", + data: { /* response data */ } + }; + } + + // Fall back to the original handler + return originalHandler(message, packageManager); + }; +}; +``` + +2. **Register the Extended Handler**: + +```typescript +// In your extension code +const customMessageHandler = extendMessageHandler(); +context.subscriptions.push( + vscode.commands.registerCommand("packageManager.handleMessage", (message) => { + return customMessageHandler(message, packageManager); + }) +); +``` + +## Integration with Other Systems + +The Package Manager can be integrated with other systems: + +### Integration with External APIs + +To integrate with external APIs: + +1. **Create an API Client**: + +```typescript +class ExternalApiClient { + private baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + async fetchPackages(): Promise { + const response = await fetch(`${this.baseUrl}/packages`); + const data = await response.json(); + + // Transform API data to PackageManagerItem format + return data.map(item => ({ + name: item.name, + description: item.description, + type: item.type, + url: item.url, + repoUrl: item.repository_url, + // Map other fields + })); + } +} +``` + +2. **Create a Source Provider for the API**: + +```typescript +class ApiSourceProvider implements SourceProvider { + private apiClient: ExternalApiClient; + + constructor(apiUrl: string) { + this.apiClient = new ExternalApiClient(apiUrl); + } + + type = "api"; + + canHandle(url: string): boolean { + return url.startsWith("api://"); + } + + async fetchItems(url: string): Promise { + return this.apiClient.fetchPackages(); + } +} +``` + +3. **Register the API Provider**: + +```typescript +// In your extension code +const registerApiProvider = (packageManager: PackageManagerManager) => { + packageManager.registerSourceProvider( + new ApiSourceProvider("https://your-api.example.com") + ); +}; +``` + +### Integration with Authentication Systems + +To integrate with authentication systems: + +1. **Create an Authentication Provider**: + +```typescript +class AuthProvider { + private token: string | null = null; + + async login(): Promise { + // Your authentication logic + this.token = "your-auth-token"; + return true; + } + + async getToken(): Promise { + if (!this.token) { + await this.login(); + } + return this.token; + } + + isAuthenticated(): boolean { + return !!this.token; + } +} +``` + +2. **Use Authentication in API Requests**: + +```typescript +class AuthenticatedApiClient extends ExternalApiClient { + private authProvider: AuthProvider; + + constructor(baseUrl: string, authProvider: AuthProvider) { + super(baseUrl); + this.authProvider = authProvider; + } + + async fetchPackages(): Promise { + const token = await this.authProvider.getToken(); + + if (!token) { + throw new Error("Authentication required"); + } + + const response = await fetch(`${this.baseUrl}/packages`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + + // Process response as before + } +} +``` + +### Integration with Local Development Tools + +To integrate with local development tools: + +1. **Create a Local Development Provider**: + +```typescript +class LocalDevProvider { + private workspacePath: string; + + constructor(workspacePath: string) { + this.workspacePath = workspacePath; + } + + async createLocalPackage(template: string, name: string): Promise { + const targetPath = path.join(this.workspacePath, name); + + // Create directory + await fs.promises.mkdir(targetPath, { recursive: true }); + + // Copy template files + // Your implementation + + return targetPath; + } + + async buildLocalPackage(packagePath: string): Promise { + // Your build implementation + return true; + } + + async testLocalPackage(packagePath: string): Promise { + // Your test implementation + return true; + } +} +``` + +2. **Integrate with the Package Manager**: + +```typescript +// In your extension code +const registerLocalDevTools = (context: vscode.ExtensionContext) => { + const workspaceFolders = vscode.workspace.workspaceFolders; + + if (!workspaceFolders) { + return; + } + + const workspacePath = workspaceFolders[0].uri.fsPath; + const localDevProvider = new LocalDevProvider(workspacePath); + + // Register commands + context.subscriptions.push( + vscode.commands.registerCommand("packageManager.createLocal", async (template, name) => { + return localDevProvider.createLocalPackage(template, name); + }), + + vscode.commands.registerCommand("packageManager.buildLocal", async (packagePath) => { + return localDevProvider.buildLocalPackage(packagePath); + }), + + vscode.commands.registerCommand("packageManager.testLocal", async (packagePath) => { + return localDevProvider.testLocalPackage(packagePath); + }) + ); +}; +``` + +## Best Practices for Extensions + +When extending the Package Manager, follow these best practices: + +### Maintainable Code + +1. **Follow the Existing Patterns**: + - Use similar naming conventions + - Follow the same code structure + - Maintain consistent error handling + +2. **Document Your Extensions**: + - Add JSDoc comments to functions and classes + - Explain the purpose of your extensions + - Document any configuration options + +3. **Write Tests**: + - Add unit tests for new functionality + - Update integration tests as needed + - Ensure test coverage remains high + +### Performance Considerations + +1. **Lazy Loading**: + - Load data only when needed + - Defer expensive operations + - Use pagination for large datasets + +2. **Efficient Data Processing**: + - Minimize data transformations + - Use memoization for expensive calculations + - Batch operations when possible + +3. **UI Responsiveness**: + - Keep the UI responsive during operations + - Show loading indicators for async operations + - Use debouncing for frequent events + +### Compatibility + +1. **VSCode API Compatibility**: + - Use stable VSCode API features + - Handle API version differences + - Test with multiple VSCode versions + +2. **Cross-Platform Support**: + - Test on Windows, macOS, and Linux + - Use path.join for file paths + - Handle file system differences + +3. **Theme Compatibility**: + - Use VSCode theme variables + - Test with light and dark themes + - Support high contrast mode + +--- + +**Previous**: [Testing Strategy](./06-testing-strategy.md) \ No newline at end of file diff --git a/cline_docs/package-manager/implementation/localization-improvements.md b/cline_docs/package-manager/implementation/localization-improvements.md new file mode 100644 index 0000000000..9df407f125 --- /dev/null +++ b/cline_docs/package-manager/implementation/localization-improvements.md @@ -0,0 +1,397 @@ +# Package Manager Localization Improvements + +## Issue Identified + +The current implementation of the Package Manager only uses English metadata (`metadata.en.yml`) for all functionality, regardless of the user's locale. While the system loads metadata files for other locales, it doesn't actually use them. The correct behavior should be: + +1. Use the locale-specific version for each package item if it is present +2. Fall back to the English version if the locale-specific version is not available +3. Skip the item if neither the locale-specific nor the English version is available + +## Implementation Changes Needed + +### 1. Add User Locale Detection + +```typescript +// Add to src/services/package-manager/types.ts +export interface LocalizationOptions { + userLocale: string; + fallbackLocale: string; +} +``` + +```typescript +// Add to src/services/package-manager/utils.ts +export function getUserLocale(): string { + // Get from VS Code API or system locale + const vscodeLocale = vscode.env.language; + // Extract just the language part (e.g., "en-US" -> "en") + return vscodeLocale.split('-')[0].toLowerCase(); +} +``` + +### 2. Modify MetadataScanner to Use Locale Preference + +```typescript +// Update MetadataScanner constructor +constructor(git?: SimpleGit, private localizationOptions?: LocalizationOptions) { + this.git = git; + this.localizationOptions = localizationOptions || { + userLocale: getUserLocale(), + fallbackLocale: 'en' + }; +} +``` + +### 3. Update Component Creation Logic + +```typescript +// Update scanDirectory method in MetadataScanner.ts +async scanDirectory(rootDir: string, repoUrl: string, sourceName?: string): Promise { + const items: PackageManagerItem[] = []; + + try { + const entries = await fs.readdir(rootDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + const componentDir = path.join(rootDir, entry.name); + const metadata = await this.loadComponentMetadata(componentDir); + + // Skip if no metadata found at all + if (!metadata) continue; + + // Get localized metadata with fallback + const localizedMetadata = this.getLocalizedMetadata(metadata); + if (!localizedMetadata) continue; + + const item = await this.createPackageManagerItem(localizedMetadata, componentDir, repoUrl, sourceName); + if (item) { + // Process package subcomponents with the same localization logic + // ...rest of the method + } + } + } catch (error) { + console.error(`Error scanning directory ${rootDir}:`, error); + } + + return items; +} +``` + +### 4. Add Localization Selection Helper + +```typescript +// Add to MetadataScanner.ts +private getLocalizedMetadata(metadata: LocalizedMetadata): ComponentMetadata | null { + const { userLocale, fallbackLocale } = this.localizationOptions; + + // First try user's locale + if (metadata[userLocale]) { + return metadata[userLocale]; + } + + // Fall back to English + if (metadata[fallbackLocale]) { + return metadata[fallbackLocale]; + } + + // No suitable metadata found + return null; +} +``` + +### 5. Update Subcomponent Processing + +```typescript +// Update the subcomponent processing in scanDirectory +if (this.isPackageMetadata(localizedMetadata)) { + // Load metadata for items listed in package metadata + if (localizedMetadata.items) { + const subcomponents = await Promise.all( + localizedMetadata.items.map(async (subItem) => { + const subPath = path.join(componentDir, subItem.path); + const subMetadata = await this.loadComponentMetadata(subPath); + + // Skip if no metadata found + if (!subMetadata) return null; + + // Get localized metadata with fallback + const localizedSubMetadata = this.getLocalizedMetadata(subMetadata); + if (!localizedSubMetadata) return null; + + return { + type: subItem.type, + path: subItem.path, + metadata: localizedSubMetadata, + lastUpdated: await this.getLastModifiedDate(subPath), + }; + }), + ); + item.items = subcomponents.filter((sub): sub is NonNullable => sub !== null); + } + + // Also scan directory for unlisted subcomponents with localization support + await this.scanPackageSubcomponents(componentDir, item); +} +``` + +### 6. Update scanPackageSubcomponents Method + +```typescript +// Update scanPackageSubcomponents in MetadataScanner.ts +private async scanPackageSubcomponents( + packageDir: string, + packageItem: PackageManagerItem, + parentPath: string = "", +): Promise { + const entries = await fs.readdir(packageDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + const subPath = path.join(packageDir, entry.name); + const relativePath = parentPath ? path.join(parentPath, entry.name) : entry.name; + + // Try to load metadata directly + const subMetadata = await this.loadComponentMetadata(subPath); + + if (subMetadata) { + const isListed = packageItem.items?.some((i) => i.path === relativePath); + + if (!isListed) { + // Get localized metadata with fallback + const localizedSubMetadata = this.getLocalizedMetadata(subMetadata); + if (localizedSubMetadata) { + const subItem = { + type: localizedSubMetadata.type, + path: relativePath, + metadata: localizedSubMetadata, + lastUpdated: await this.getLastModifiedDate(subPath), + }; + packageItem.items = packageItem.items || []; + packageItem.items.push(subItem); + } + } + } + + // Recursively scan this directory + await this.scanPackageSubcomponents(subPath, packageItem, relativePath); + } +} +``` + +### 7. Update PackageManagerManager to Pass Locale + +```typescript +// Update PackageManagerManager.ts +constructor(private readonly context: vscode.ExtensionContext) { + const userLocale = getUserLocale(); + this.gitFetcher = new GitFetcher(context, { userLocale, fallbackLocale: 'en' }); +} +``` + +## Test Cases + +### Unit Tests + +1. **Test Locale Fallback Logic** + +```typescript +describe('Localization Fallback', () => { + let metadataScanner: MetadataScanner; + + beforeEach(() => { + // Mock fs and other dependencies + }); + + test('should use user locale when available', async () => { + // Setup mock metadata with both user locale and English + const mockMetadata = { + 'en': { name: 'English Name', description: 'English Description' }, + 'fr': { name: 'Nom Français', description: 'Description Française' } + }; + + // Initialize with French locale + metadataScanner = new MetadataScanner(null, { userLocale: 'fr', fallbackLocale: 'en' }); + + // Call the getLocalizedMetadata method + const result = metadataScanner['getLocalizedMetadata'](mockMetadata); + + // Expect French metadata to be used + expect(result.name).toBe('Nom Français'); + expect(result.description).toBe('Description Française'); + }); + + test('should fall back to English when user locale not available', async () => { + // Setup mock metadata with only English + const mockMetadata = { + 'en': { name: 'English Name', description: 'English Description' } + }; + + // Initialize with French locale + metadataScanner = new MetadataScanner(null, { userLocale: 'fr', fallbackLocale: 'en' }); + + // Call the getLocalizedMetadata method + const result = metadataScanner['getLocalizedMetadata'](mockMetadata); + + // Expect English metadata to be used as fallback + expect(result.name).toBe('English Name'); + expect(result.description).toBe('English Description'); + }); + + test('should return null when neither user locale nor English available', async () => { + // Setup mock metadata with neither user locale nor English + const mockMetadata = { + 'de': { name: 'Deutscher Name', description: 'Deutsche Beschreibung' } + }; + + // Initialize with French locale + metadataScanner = new MetadataScanner(null, { userLocale: 'fr', fallbackLocale: 'en' }); + + // Call the getLocalizedMetadata method + const result = metadataScanner['getLocalizedMetadata'](mockMetadata); + + // Expect null result + expect(result).toBeNull(); + }); +}); +``` + +2. **Test Component Loading with Localization** + +```typescript +describe('Component Loading with Localization', () => { + let metadataScanner: MetadataScanner; + + beforeEach(() => { + // Mock fs and other dependencies + }); + + test('should load components with user locale preference', async () => { + // Setup mock directory structure with multiple locales + mockFs.readdir.mockImplementation((dir, options) => { + if (dir === '/test/repo') { + return Promise.resolve([ + { name: 'component1', isDirectory: () => true }, + { name: 'component2', isDirectory: () => true } + ]); + } + return Promise.resolve([]); + }); + + // Mock loadComponentMetadata to return different locales + jest.spyOn(MetadataScanner.prototype, 'loadComponentMetadata').mockImplementation((dir) => { + if (dir === '/test/repo/component1') { + return Promise.resolve({ + 'en': { name: 'Component 1 EN', description: 'Description EN', type: 'mode' }, + 'fr': { name: 'Component 1 FR', description: 'Description FR', type: 'mode' } + }); + } else if (dir === '/test/repo/component2') { + return Promise.resolve({ + 'en': { name: 'Component 2 EN', description: 'Description EN', type: 'mcp server' } + }); + } + return Promise.resolve(null); + }); + + // Initialize with French locale + metadataScanner = new MetadataScanner(null, { userLocale: 'fr', fallbackLocale: 'en' }); + + // Scan directory + const items = await metadataScanner.scanDirectory('/test/repo', 'https://example.com'); + + // Expect French for component1, English for component2 + expect(items.length).toBe(2); + expect(items[0].name).toBe('Component 1 FR'); + expect(items[1].name).toBe('Component 2 EN'); + }); +}); +``` + +3. **Test Subcomponent Processing with Localization** + +```typescript +describe('Subcomponent Processing with Localization', () => { + // Similar tests for subcomponents +}); +``` + +### Integration Tests + +1. **Test End-to-End Localization Flow** + +```typescript +describe('End-to-End Localization', () => { + test('should display components in user locale with fallback', async () => { + // Setup test repository with multiple locales + // Initialize PackageManagerManager with specific locale + // Verify that components are displayed in the correct locale + }); +}); +``` + +2. **Test with Real Package Repository** + +```typescript +describe('Real Package Repository with Localization', () => { + test('should handle real-world package repository with multiple locales', async () => { + // Use a real package repository with multiple locales + // Verify correct locale selection and fallback + }); +}); +``` + +## UI Changes + +1. **Add Locale Selector in UI (Optional Enhancement)** + +```typescript +// Add to webview-ui/src/components/package-manager/PackageManagerView.tsx +const [currentLocale, setCurrentLocale] = useState(getUserLocale()); + +// Add locale selector dropdown + +``` + +## Documentation Updates + +Update the documentation to reflect the correct localization behavior: + +```markdown +### Localization Support + +You can provide metadata in multiple languages by using locale-specific files: + +- `metadata.en.yml` - English metadata (required as fallback) +- `metadata.es.yml` - Spanish metadata +- `metadata.fr.yml` - French metadata + +**Important Notes on Localization:** +- Only files with the pattern `metadata.{locale}.yml` are supported +- The Package Manager will display metadata in the user's locale if available +- If the user's locale is not available, it will fall back to English +- The English locale (`metadata.en.yml`) is required as a fallback +- Files without a locale code (e.g., just `metadata.yml`) are not supported +``` + +## Implementation Plan + +1. Add localization options and user locale detection +2. Modify MetadataScanner to use locale preference with fallback +3. Update component creation logic to handle localization +4. Add tests to verify localization behavior +5. Update documentation to reflect the correct behavior +6. (Optional) Add UI controls for locale selection \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/01-introduction.md b/cline_docs/package-manager/user-guide/01-introduction.md new file mode 100644 index 0000000000..c83664c6b9 --- /dev/null +++ b/cline_docs/package-manager/user-guide/01-introduction.md @@ -0,0 +1,54 @@ +# Introduction to Package Manager + +## Overview and Purpose + +The Package Manager is a powerful feature in Roo Code that allows you to discover, browse, and utilize various components to enhance your development experience. It serves as a centralized hub for accessing: + +- **Modes**: Specialized AI assistants with different capabilities +- **MCP Servers**: Model Context Protocol servers that provide additional functionality +- **Prompts**: Pre-configured instructions for specific tasks +- **Packages**: Collections of related components + +The Package Manager simplifies the process of extending Roo Code's capabilities by providing a user-friendly interface to find, filter, and add new components to your environment. + +## Key Features and Capabilities + +### Component Discovery +- Browse a curated collection of components +- View detailed information about each component +- Explore subcomponents within packages + +### Search and Filter +- Search by name and description +- Filter by component type (mode, MCP server, etc.) +- Use tags to find related components +- Combine search and filters for precise results + +### Component Details +- View comprehensive information about each component +- See version information +- Access source repositories directly +- Explore subcomponents organized by type + +### Package Management +- Add new components to your environment +- Manage custom package sources +- Create and contribute your own packages + +## How to Access the Package Manager + +The Package Manager can be accessed through the Roo Code extension in VS Code: + +1. Open VS Code with the Roo Code extension installed +2. Click on the Roo Code icon in the activity bar +3. Select "Package Manager" from the available options + +Alternatively, you can use the Command Palette: + +1. Press `Ctrl+Shift+P` (Windows/Linux) or `Cmd+Shift+P` (Mac) to open the Command Palette +2. Type "Roo Code: Open Package Manager" +3. Press Enter to open the Package Manager + +--- + +**Next**: [Browsing Packages](./02-browsing-packages.md) \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/02-browsing-packages.md b/cline_docs/package-manager/user-guide/02-browsing-packages.md new file mode 100644 index 0000000000..34ecbb9c8d --- /dev/null +++ b/cline_docs/package-manager/user-guide/02-browsing-packages.md @@ -0,0 +1,138 @@ +# Browsing Packages + +## Understanding the Package Manager Interface + +The Package Manager interface is designed to provide a clean, intuitive experience for discovering and exploring available components. The main interface consists of several key areas: + +### Main Sections + +1. **Navigation Tabs** + - **Browse**: View all available components + - **Sources**: Manage package sources + +2. **Filter Panel** + - Type filters (Modes, MCP Servers, Packages, etc.) + - Search box + - Tag filters + +3. **Results Area** + - Package cards displaying component information + - Sorting options + +### Interface Layout + +``` +┌─────────────────────────────────────────────────────────┐ +│ [Browse] [Sources] │ +├─────────────────────────────────────────────────────────┤ +│ FILTERS │ +│ Types: □ Mode □ MCP Server □ Package □ Prompt │ +│ Search: [ ] │ +│ Tags: [Tag cloud] │ +├─────────────────────────────────────────────────────────┤ +│ PACKAGE CARDS │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Package Name [Type] │ │ +│ │ by Author │ │ +│ │ │ │ +│ │ Description text... │ │ +│ │ │ │ +│ │ [Tags] [Tags] [Tags] │ │ +│ │ │ │ +│ │ v1.0.0 Apr 12, 2025 [View] │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Another Package [Type] │ │ +│ │ ... │ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Package Cards and Information Displayed + +Each package in the Package Manager is represented by a card that contains essential information about the component: + +### Card Elements + +1. **Header Section** + - **Package Name**: The name of the component + - **Author**: The creator or maintainer of the component (if available) + - **Type Badge**: Visual indicator of the component type (Mode, MCP Server, etc.) + +2. **Description** + - A brief overview of the component's purpose and functionality + +3. **Tags** + - Clickable tags that categorize the component + - Can be used for filtering similar components + +4. **Metadata** + - **Version**: The current version of the component (if available) + - **Last Updated**: When the component was last modified (if available) + +5. **Actions** + - **View**: Button to access the component's source repository or documentation + +6. **Details Section** (expandable) + - Shows subcomponents grouped by type + - Displays additional information when expanded + +### Example Card + +``` +┌─────────────────────────────────────────────────────┐ +│ Data Platform Package [Package] │ +│ by Roo Team │ +│ │ +│ A comprehensive data processing and analysis │ +│ package with tools for ETL, visualization, and ML. │ +│ │ +│ [data] [analytics] [machine-learning] │ +│ │ +│ v2.1.0 Apr 10, 2025 [View] │ +│ │ +│ ▼ Component Details │ +│ MCP Servers: │ +│ 1. Data Validator - Validates data formats │ +│ 2. ML Predictor - Makes predictions on data │ +│ │ +│ Modes: │ +│ 1. Data Analyst - Helps with data analysis │ +│ 2. ETL Engineer - Assists with data pipelines │ +└─────────────────────────────────────────────────────┘ +``` + +## Navigating Between Packages + +The Package Manager provides several ways to navigate through the available packages: + +### Navigation Methods + +1. **Scrolling** + - Scroll through the list of package cards to browse all available components + +2. **Filtering** + - Use the filter panel to narrow down the displayed packages + - Click on type filters to show only specific component types + - Enter search terms to find packages by name or description + - Click on tags to filter by specific categories + +3. **Sorting** + - Sort packages by name or last updated date + - Toggle between ascending and descending order + +4. **Tab Navigation** + - Switch between "Browse" and "Sources" tabs to manage package sources + +### Keyboard Navigation + +For accessibility and efficiency, the Package Manager supports keyboard navigation: + +- **Tab**: Move focus between interactive elements +- **Space/Enter**: Activate buttons or toggle filters +- **Arrow Keys**: Navigate between package cards +- **Escape**: Close expanded details or clear filters + +--- + +**Previous**: [Introduction to Package Manager](./01-introduction.md) | **Next**: [Searching and Filtering](./03-searching-and-filtering.md) \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/03-searching-and-filtering.md b/cline_docs/package-manager/user-guide/03-searching-and-filtering.md new file mode 100644 index 0000000000..434e40f62c --- /dev/null +++ b/cline_docs/package-manager/user-guide/03-searching-and-filtering.md @@ -0,0 +1,132 @@ +# Searching and Filtering + +The Package Manager provides powerful search and filtering capabilities to help you quickly find the components you need. This guide explains how to effectively use these features to narrow down your search results. + +## Using the Search Functionality + +The search box allows you to find components by matching text in various fields: + +### What Gets Searched + +When you enter a search term, the Package Manager looks for matches in: + +1. **Component Name**: The primary identifier of the component +2. **Description**: The detailed explanation of the component's purpose +3. **Subcomponent Names and Descriptions**: Text within nested components + +### Search Features + +- **Case Insensitive**: Searches ignore letter case for easier matching +- **Whitespace Insensitive**: Extra spaces are normalized in the search +- **Partial Matching**: Finds results that contain your search term anywhere in the text +- **Instant Results**: Results update as you type +- **Match Highlighting**: Matching subcomponents are highlighted and expanded automatically + +### Search Implementation + +The search uses a simple string contains match that is case and whitespace insensitive. This means: + +- "Data" will match "data", "DATA", "Data", etc. +- "machine learning" will match "Machine Learning", "machine-learning", etc. +- Partial words will match: "valid" will match "validation", "validator", etc. + +### Search Tips + +- Use specific, distinctive terms to narrow results +- Try different variations if you don't find what you're looking for +- Search for technology names or specific functionality +- Look for highlighted "match" indicators in expanded details sections + +### Example Searches + +| Search Term | Will Find | +|-------------|-----------| +| "data" | Components with "data" in their name, description, or subcomponents | +| "validator" | Components that include validation functionality or have validator subcomponents | +| "machine learning" | Components related to machine learning technology | + +## Filtering by Package Type + +The type filter allows you to focus on specific categories of components: + +### Available Type Filters + +- **Mode**: AI assistant personalities with specialized capabilities +- **MCP Server**: Model Context Protocol servers that provide additional functionality +- **Package**: Collections of related components +- **Prompt**: Pre-configured instructions for specific tasks + +### Using Type Filters + +1. Click on a type checkbox to show only components of that type +2. Select multiple types to show components that match any of the selected types +3. Clear all type filters to show all components again + +### Type Filter Behavior + +- Type filters apply to the primary component type, not subcomponents +- The type is displayed as a badge on each package card +- Type filtering can be combined with search terms and tag filters + +## Using Tags for Filtering + +Tags provide a way to filter components by category, technology, or purpose: + +### Tag Functionality + +- Tags appear as clickable buttons on package cards +- Clicking a tag activates it as a filter +- Active tag filters are highlighted +- Components must have at least one of the selected tags to be displayed + +### Finding and Using Tags + +1. Browse through package cards to discover available tags +2. Click on a tag to filter for components with that tag +3. Click on additional tags to expand your filter (components with any of the selected tags will be shown) +4. Click on an active tag to deactivate it + +### Common Tags + +- Technology areas: "data", "web", "security", "ai" +- Programming languages: "python", "javascript", "typescript" +- Functionality: "testing", "documentation", "analysis" +- Domains: "finance", "healthcare", "education" + +## Combining Search and Filters + +For the most precise results, you can combine search terms, type filters, and tag filters: + +### How Combined Filtering Works + +1. **AND Logic Between Filter Types**: Components must match the search term AND the selected types AND have at least one of the selected tags +2. **OR Logic Within Tag Filters**: Components must have at least one of the selected tags + +### Combined Filter Examples + +| Search Term | Type Filter | Tag Filter | Will Find | +|-------------|-------------|------------|-----------| +| "data" | MCP Server | "analytics" | MCP Servers related to data analytics | +| "test" | Mode | "automation", "quality" | Test automation or quality-focused modes | +| "visualization" | Package | "dashboard", "chart" | Packages for creating dashboards or charts | + +### Clearing Filters + +To reset your search and start over: + +1. Clear the search box +2. Uncheck all type filters +3. Deactivate all tag filters by clicking on them + +### Filter Status Indicators + +The Package Manager provides visual feedback about your current filters: + +- Active type filters are checked +- Active tag filters are highlighted +- The search box shows your current search term +- Result counts may be displayed to show how many items match your filters + +--- + +**Previous**: [Browsing Packages](./02-browsing-packages.md) | **Next**: [Working with Package Details](./04-working-with-details.md) \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/04-working-with-details.md b/cline_docs/package-manager/user-guide/04-working-with-details.md new file mode 100644 index 0000000000..06ae2b5b3e --- /dev/null +++ b/cline_docs/package-manager/user-guide/04-working-with-details.md @@ -0,0 +1,139 @@ +# Working with Package Details + +Package Manager items often contain multiple components organized in a hierarchical structure. This guide explains how to work with the details section of package cards to explore and understand the components within each package. + +## Expanding Package Details + +Most packages in the Package Manager contain subcomponents that are hidden by default to keep the interface clean. You can expand these details to see what's inside each package: + +### How to Expand Details + +1. Look for the "Component Details" section at the bottom of a package card +2. Click on the section header or the chevron icon (▶) to expand it +3. The section will animate open, revealing the components inside the package +4. Click again to collapse the section when you're done + +### Automatic Expansion + +The details section will expand automatically when: + +- Your search term matches text in a subcomponent +- This is the only condition for automatic expansion + +### Details Section Badge + +The details section may display a badge with additional information: + +- **Match count**: When your search term matches subcomponents, a badge shows how many matches were found (e.g., "3 matches") +- This helps you quickly identify which packages contain relevant subcomponents + +## Understanding Component Types + +Components within packages are grouped by their type to make them easier to find and understand: + +### Common Component Types + +1. **Modes** + - AI assistant personalities with specialized capabilities + - Examples: Code Mode, Architect Mode, Debug Mode + +2. **MCP Servers** + - Model Context Protocol servers that provide additional functionality + - Examples: File Analyzer, Data Validator, Image Generator + +3. **Prompts** + - Pre-configured instructions for specific tasks + - Examples: Code Review, Documentation Generator, Test Case Creator + +4. **Packages** + - Nested collections of related components + - Can contain any of the other component types + +### Type Presentation + +Each type section in the details view includes: + +- A header with the type name (pluralized, e.g., "MCP Servers") +- A numbered list of components of that type +- Each component's name and description + +## Viewing Subcomponents + +The details section organizes subcomponents in a clear, structured format: + +### Subcomponent List Format + +``` +Component Details + Type Name: + 1. Component Name - Description text goes here + 2. Another Component - Its description + + Another Type: + 1. First Component - Description + 2. Second Component - Description +``` + +### Subcomponent Information + +Each subcomponent in the list displays: + +1. **Number**: Sequential number within its type group +2. **Name**: The name of the subcomponent +3. **Description**: A brief explanation of the subcomponent's purpose (if available) +4. **Match Indicator**: A "match" badge appears next to items that match your search term + +### Navigating Subcomponents + +- Scroll within the details section to see all subcomponents +- Components are grouped by type, making it easier to find specific functionality +- Long descriptions may be truncated with an ellipsis (...) to save space (limited to 100 characters) + +## Matching Search Terms in Subcomponents + +One of the most powerful features of the Package Manager is the ability to search within subcomponents: + +### How Subcomponent Matching Works + +1. Enter a search term in the search box +2. The Package Manager searches through all subcomponent names and descriptions +3. Packages with matching subcomponents remain visible in the results +4. The details section automatically expands for packages with matches +5. Matching subcomponents are highlighted and marked with a "match" badge + +### Visual Indicators for Matches + +When a subcomponent matches your search: + +- The component name is highlighted in a different color +- A "match" badge appears next to the component +- The details section automatically expands +- A badge on the details section header shows the number of matches + +### Search Implementation + +The search uses a simple string contains match that is case-insensitive: + +- "validator" will match "Data Validator", "Validator Tool", etc. +- "valid" will match "validation", "validator", etc. +- The search will match any part of the name or description + +### Example Scenario + +If you search for "validator": + +1. Packages containing components with "validator" in their name or description remain visible +2. The details section expands automatically for packages with matching subcomponents +3. Components like "Data Validator" or those with "validation" in their description are highlighted +4. A badge might show "2 matches" if two subcomponents match your search term + +### Benefits of Subcomponent Matching + +- Find functionality buried deep within packages +- Discover relationships between components +- Identify packages that contain specific tools or capabilities +- Locate similar components across different packages + +--- + +**Previous**: [Searching and Filtering](./03-searching-and-filtering.md) | **Next**: [Adding Packages](./05-adding-packages.md) \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/05-adding-packages.md b/cline_docs/package-manager/user-guide/05-adding-packages.md new file mode 100644 index 0000000000..58d7812278 --- /dev/null +++ b/cline_docs/package-manager/user-guide/05-adding-packages.md @@ -0,0 +1,6 @@ +**Important Notes on Localization:** +- Only files with the pattern `metadata.{locale}.yml` are supported +- The Package Manager will display metadata in the user's locale if available +- If the user's locale is not available, it will fall back to English +- The English locale (`metadata.en.yml`) is required as a fallback +- Files without a locale code (e.g., just `metadata.yml`) are not supported \ No newline at end of file diff --git a/cline_docs/package-manager/user-guide/06-adding-custom-sources.md b/cline_docs/package-manager/user-guide/06-adding-custom-sources.md new file mode 100644 index 0000000000..951cdfb78e --- /dev/null +++ b/cline_docs/package-manager/user-guide/06-adding-custom-sources.md @@ -0,0 +1,198 @@ +# Adding Custom Package Sources + +The Package Manager allows you to extend its functionality by adding custom package sources. This guide explains how to set up and manage your own package repositories to access additional components beyond the default offerings. + +## Setting up a Package Source Repository + +A package source repository is a Git repository that contains packages organized in a specific structure. You can create your own repository to host custom packages: + +### Repository Requirements + +1. **Proper Structure**: The repository must follow the required directory structure +2. **Valid Metadata**: Each package must include properly formatted metadata files +3. **Git Repository**: The source must be a Git repository accessible via HTTPS + +### Creating a New Repository + +1. Create a new repository on GitHub, GitLab, or another Git hosting service +2. Initialize the repository with a README.md file +3. Clone the repository to your local machine: + +```bash +git clone https://github.com/your-username/your-package-repo.git +cd your-package-repo +``` + +4. Create the basic repository structure: + +```bash +mkdir -p packages modes "mcp servers" prompts +touch metadata.en.yml +``` + +5. Add repository metadata to `metadata.en.yml`: + +```yaml +name: "Your Repository Name" +description: "A collection of custom packages for Roo Code" +version: "1.0.0" +``` + +6. Commit and push the initial structure: + +```bash +git add . +git commit -m "Initialize package repository structure" +git push origin main +``` + +## Required Structure and Metadata + +A package source repository must follow a specific structure to be properly recognized by the Package Manager: + +### Repository Structure + +``` +repository-root/ +├── metadata.en.yml # Repository metadata +├── README.md # Repository documentation +├── packages/ # Directory for package components +│ ├── package-1/ +│ │ ├── metadata.en.yml # Package metadata +│ │ └── README.md +│ └── package-2/ +│ ├── metadata.en.yml +│ └── README.md +├── modes/ # Directory for mode components +│ └── custom-mode/ +│ └── metadata.en.yml +├── mcp servers/ # Directory for MCP server components +│ └── custom-server/ +│ └── metadata.en.yml +└── prompts/ # Directory for prompt components + └── custom-prompt/ + └── metadata.en.yml +``` + +### Repository Metadata + +The root `metadata.en.yml` file describes the repository itself: + +```yaml +name: "Custom Components Repository" +description: "A collection of specialized components for data science workflows" +version: "1.0.0" +author: "Your Name or Organization" +tags: + - custom + - data-science +``` + +### Component Organization + +- Components should be organized by type in their respective directories +- Each component must have its own directory containing a metadata file +- Components can be nested within packages +- Follow the same structure as described in [Adding Packages](./05-adding-packages.md) + +## Adding Sources to Roo Code + +Once you have a properly structured package source repository, you can add it to your Roo Code Package Manager: + +### Default Package Source + +Roo Code comes with a default package source: +- URL: `https://github.com/RooVetGit/Roo-Code-Packages` +- Name: "Roo Code Package Manager Template" +- This source is enabled by default + +### Adding a New Source + +1. Open VS Code with the Roo Code extension +2. Navigate to the Package Manager +3. Switch to the "Sources" tab +4. Click the "Add Source" button +5. Enter the repository URL: + - Format: `https://github.com/username/repository.git` + - Example: `https://github.com/your-username/your-package-repo.git` +6. Click "Add" to save the source + +### Managing Sources + +The "Sources" tab provides several options for managing your package sources: + +1. **Enable/Disable**: Toggle sources on or off without removing them +2. **Remove**: Delete a source from your configuration +3. **Refresh**: Update the package list from all enabled sources +4. **View Details**: See information about each source + +### Source Caching and Refreshing + +Package Manager sources are cached to improve performance: + +- **Cache Duration**: Sources are cached for 1 hour (3600000 ms) +- **Force Refresh**: To force an immediate refresh of a source: + 1. Go to the "Sources" tab + 2. Click the "Refresh" button next to the source you want to update + 3. This will bypass the cache and fetch the latest data from the repository + +### Troubleshooting Sources + +If a source isn't loading properly: + +1. Check that the repository URL is correct +2. Ensure the repository follows the required structure +3. Look for error messages in the Package Manager interface +4. Try refreshing the sources list +5. Disable and re-enable the source + +## Creating Private Sources + +For team or organization use, you might want to create private package sources: + +### Private Repository Setup + +1. Create a private repository on your Git hosting service +2. Follow the same structure requirements as public repositories +3. Set up appropriate access controls for your team members + +### Authentication Options + +To access private repositories, you may need to: + +1. Configure Git credentials on your system +2. Use a personal access token with appropriate permissions +3. Set up SSH keys for authentication + +### Organization Best Practices + +For teams and organizations: + +1. Designate maintainers responsible for the package source +2. Establish quality standards for contributed packages +3. Create a review process for new additions +4. Document usage guidelines for team members +5. Consider implementing versioning for your packages + +## Using Multiple Sources + +The Package Manager supports multiple package sources simultaneously: + +### Benefits of Multiple Sources + +- Access components from different providers +- Separate internal and external components +- Test new packages before contributing them to the main repository +- Create specialized sources for different projects or teams + +### Source Management Strategy + +1. Keep the default source enabled for core components +2. Add specialized sources for specific needs +3. Create a personal source for testing and development +4. Disable sources temporarily when not needed +5. Regularly update sources to get the latest components + +--- + +**Previous**: [Adding Packages](./05-adding-packages.md) | **Next**: [Package Manager Architecture](../implementation/01-architecture.md) \ No newline at end of file