diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index acf1eca..6c62fea 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Curated Copilot plugins for enterprise C# development, Coalesce framework, and Vue 3 with Vuetify. Designed to support SOLID principles, best practices, and architectural excellence.", - "version": "1.0.0", + "version": "1.0.1", "homepage": "https://intellitect.github.io/IntelliPlugins", "repository": "https://github.com/IntelliTect/IntelliPlugins", "license": "MIT" @@ -16,7 +16,7 @@ "name": "solid-principles", "displayName": "SOLID Principles & Architecture", "description": "Enterprise-grade guidance on SOLID principles, architecture patterns, and code quality for maintainable C# projects.", - "version": "1.0.0", + "version": "1.0.1", "category": "Enterprise", "source": "./plugins/solid-principles", "keywords": [ @@ -31,7 +31,7 @@ "name": "testing-essentials", "displayName": "Testing Essentials", "description": "Comprehensive testing best practices including unit testing strategies, test naming conventions, and mock frameworks.", - "version": "1.0.0", + "version": "1.0.1", "category": "Enterprise", "source": "./plugins/testing-essentials", "keywords": ["testing", "unit-tests", "TDD", "best-practices", "xUnit"] @@ -40,7 +40,7 @@ "name": "csharp-best-practices", "displayName": "C# Best Practices", "description": "C#-specific language patterns, naming conventions, error handling, dependency injection, and async best practices.", - "version": "1.0.0", + "version": "1.0.1", "category": "Language", "source": "./plugins/csharp-best-practices", "keywords": [ @@ -56,7 +56,7 @@ "name": "coalesce-accelerator", "displayName": "Coalesce Accelerator", "description": "Streamline full-stack Coalesce framework development with code generation, data binding, and EF Core migration workflows.", - "version": "1.0.0", + "version": "1.0.1", "category": "Framework", "source": "./plugins/coalesce-accelerator", "keywords": [ @@ -71,7 +71,7 @@ "name": "vuetify-components", "displayName": "Vuetify Components & Patterns", "description": "Vue 3 and Vuetify best practices for building professional UI components with accessibility and UX consistency.", - "version": "1.0.0", + "version": "1.0.1", "category": "Framework", "source": "./plugins/vuetify-components", "keywords": [ @@ -87,7 +87,7 @@ "name": "enterprise-bug-fixing", "displayName": "Enterprise Bug Fixing Workflow", "description": "Structured bug resolution workflow with test-first approach, Azure DevOps integration, and comprehensive validation.", - "version": "1.0.0", + "version": "1.0.1", "category": "Specialized", "source": "./plugins/enterprise-bug-fixing", "keywords": [ diff --git a/docs/index.md b/docs/index.md index 1af55af..eea2fc4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,52 +8,10 @@ Get started with IntelliPlugins in minutes: 1. **[Installation Guide](plugins/installation-guide.md)** - Set up plugins in your environment 2. **[Getting Started](guides/getting-started.md)** - Your first steps with Copilot plugins -3. **[Instructions](instructions/index.md)** - One-click install coding standards for VS Code -4. **[Prompts](prompts/index.md)** - One-click install reusable prompt files for VS Code -## Available Plugins +## Contributing -### Enterprise-Generic Plugins - -These plugins apply best practices across any C# project: - -- **[SOLID Principles & Architecture](plugins/solid-principles.md)** - Enterprise-grade architecture patterns -- **[Testing Essentials](plugins/testing-essentials.md)** - Comprehensive testing best practices -- **[C# Best Practices](plugins/csharp-best-practices.md)** - Language-specific patterns and conventions - -### Framework-Specific Plugins - -Tools optimized for IntelliTect's tech stack: - -- **[Coalesce Accelerator](plugins/coalesce-accelerator.md)** - Full-stack Coalesce development productivity -- **[Vuetify Components](plugins/vuetify-components.md)** - Vue 3 & Vuetify UI patterns - -### Specialized Plugins - -Domain-specific workflows: - -- **[Enterprise Bug Fixing](plugins/enterprise-bug-fixing.md)** - Structured bug resolution with Azure DevOps - -## Guides & Resources - -- **[Getting Started](guides/getting-started.md)** - New to Copilot plugins? -- **[Usage Examples](guides/usage-examples.md)** - Real-world scenarios and patterns - -## Instructions & Prompts - -Lightweight Copilot files you can install directly into VS Code with one click — no copy-pasting required. - -- **[Instructions](instructions/index.md)** - Automatically apply coding standards to matching files (e.g., C# conventions on `**/*.cs`) -- **[Prompts](prompts/index.md)** - Reusable prompt templates for common workflows (e.g., XUnit best practices) - -## Key Features - -**Enterprise Focus** - Built for SOLID principles and architectural excellence -**Coalesce Optimized** - Streamline full-stack development -**Vue 3 Ready** - Modern Vue and Vuetify patterns -**Test-First** - Emphasis on quality and testing -**One-Click Install** - Instructions and prompts install directly into VS Code -**Open Source** - Community-driven, MIT licensed +Have ideas for new content or improvements? Open a PR or [open an issue](https://github.com/IntelliTect/IntelliPlugins/issues). ## Resources diff --git a/docs/toc.yml b/docs/toc.yml index bff400a..84dd57f 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -24,12 +24,6 @@ href: instructions/index.md - name: C# Development href: instructions/csharp.md -- name: Prompts - items: - - name: Overview - href: prompts/index.md - - name: XUnit Best Practices - href: prompts/csharp-xunit.md - name: Guides items: - name: Getting Started diff --git a/plugins/coalesce-accelerator/README.md b/plugins/coalesce-accelerator/README.md index 6226953..e6960cf 100644 --- a/plugins/coalesce-accelerator/README.md +++ b/plugins/coalesce-accelerator/README.md @@ -4,242 +4,63 @@ Streamline full-stack Coalesce framework development with code generation, data ## Overview -The **Coalesce Accelerator** plugin accelerates development with the [Coalesce framework](https://coalesce.intellitect.com/), a low-code framework that generates REST APIs, TypeScript clients, and Vue 3 components from C# models. This plugin provides expert guidance on: - -- **Full-stack development patterns** — from C# models to Vue 3 UI -- **Code generation workflows** — Coalesce code generation and TypeScript synchronization -- **EF Core best practices** — model design, migrations, and relationships -- **Data binding integration** — Vue 3 reactivity with Coalesce-generated types -- **Common enterprise patterns** — pagination, filtering, real-time updates +The **Coalesce Accelerator** plugin accelerates development with the [Coalesce framework](https://coalesce.intellitect.com/), which generates REST APIs, TypeScript clients, and Vue 3 components from C# models. ## Quick Start -### Installation - ```bash copilot plugin install coalesce-accelerator@IntelliPlugins ``` -### First-Time Workflow - -1. **Create a Coalesce project** — Set up a new .NET web application with Coalesce -2. **Design your data model** — Create C# entities and configure EF Core -3. **Run code generation** — Execute `coalesce_generate` to create APIs and TypeScript types -4. **Build Vue 3 UI** — Use generated types in Vue components with Vuetify -5. **Deploy and iterate** — Test end-to-end, then add features and regenerate - -## Key Features - -### Full-Stack Development Patterns - -- Coalesce project structure and conventions -- Model design for API generation -- Navigation properties and relationships -- DTOs and projection strategies - -### Code Generation Workflows - -- Automated REST API creation from C# models -- TypeScript type definitions synchronized with C# models -- Generated validation rules and constraints -- Regeneration after schema changes - -### Entity Framework Core Patterns - -- Model-first development with Coalesce -- One-to-many, many-to-many, and self-referential relationships -- Lazy loading, eager loading, and explicit loading strategies -- Database migrations with `dotnet ef` - -### Vue 3 Data Binding - -- Integration with Coalesce-generated TypeScript types -- Reactive form binding with Vuetify components -- List management and filtering -- Real-time updates with SignalR - -### Production-Ready Workflows - -- Complex filtering and search -- Pagination and performance optimization -- Role-based access control and authorization -- Error handling and validation - -## Common Scenarios - -### Adding a New Entity with Full-Stack Propagation - -```plaintext -1. Create C# model in the server project -2. Add DbSet to AppDbContext -3. Configure relationships and data annotations -4. Run `coalesce_generate` -5. Use generated TypeScript types in Vue components -6. Create Vuetify form/table for the new entity -``` - -### Updating an Existing Model - -```plaintext -1. Modify the C# model (properties, relationships, validation) -2. Create EF Core migration: `dotnet ef migrations add [MigrationName]` -3. Update AppDbContext if needed -4. Run `coalesce_generate` to update APIs and TypeScript types -5. Verify generated TS types match component expectations -6. Update Vue components as needed -7. Run `dotnet build` and verify full-stack compilation -``` - -### Implementing Complex Filtering - -```plaintext -1. Define filter properties on the model -2. Add [Bind] and [Search] attributes for Coalesce -3. Regenerate to update API endpoints -4. Use generated filters in Vue with Coalesce list bindings -5. Test filtering with Vuetify table or custom filters -``` - -## Documentation Links - -### Official Resources - -- **[Coalesce Documentation](https://coalesce.intellitect.com/)** — Official guides, API docs, examples -- **[Coalesce GitHub](https://github.com/IntelliTect/Coalesce)** — Source code and issue tracking -- **[Vue 3 Documentation](https://vuejs.org/)** — Vue 3 fundamentals -- **[Vuetify Documentation](https://vuetifyjs.com/)** — Vue 3 component library - -### Entity Framework Core +## Skills -- **[EF Core Documentation](https://learn.microsoft.com/en-us/ef/core/)** — Model design, migrations, queries -- **[EF Core Relationships](https://learn.microsoft.com/en-us/ef/core/modeling/relationships/)** — Relationship patterns +- **`setup-coalesce-project`** — Create a new project with the official template or add Coalesce to an existing app +- **`scaffold-entity`** — Add a new entity class with EF Core annotations, relationships, and DbContext registration +- **`generate-migration`** — Create and apply an EF Core migration after model changes +- **`run-code-generation`** — Run `dotnet coalesce` to regenerate TypeScript types and API clients +- **`add-data-source`** — Create a custom `IDataSource` for filtered or user-scoped data access -## Instruction Guides +## Coalesce MCP Server -This plugin includes three comprehensive instruction files: +Projects from the Coalesce template include MCP configuration automatically. To add it manually, add to your VS Code workspace or user MCP config: -### 1. **Coalesce Workflows** (`coalesce-workflows.md`) - -- Project setup and initialization -- Model and DbContext creation -- Code generation process and troubleshooting -- TypeScript type synchronization -- Best practices for model design - -### 2. **EF Core Patterns** (`ef-core-patterns.md`) - -- Model design for Coalesce -- Relationships (one-to-many, many-to-many, self-referential) -- Data annotations and configurations -- Lazy vs eager loading strategies -- Validation and constraints -- Migrations and schema management - -### 3. **Code Generation** (`code-generation.md`) - -- What Coalesce generates (APIs, TypeScript, validation) -- Running `coalesce_generate` and understanding output -- Generated file structure and organization -- TypeScript type definitions and API clients -- Regeneration workflows after model changes -- Customization and extension points - -## Related Plugins - -- **[C# Best Practices](https://intellitect.github.io/IntelliPlugins//plugins/csharp-best-practices)** — SOLID principles and architectural patterns for .NET -- **[Vuetify Components](https://intellitect.github.io/IntelliPlugins//plugins/vuetify-components)** — Material Design UI component library for Vue 3 -- **[Testing Essentials](https://intellitect.github.io/IntelliPlugins//plugins/testing-essentials)** — Unit, integration, and E2E testing strategies - -## Architecture Highlights - -### What Coalesce Generates - -For a model like: - -```csharp -public class Person +```json { - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public DateTime DateOfBirth { get; set; } - public ICollection Companies { get; set; } -} -``` - -Coalesce automatically generates: - -- **REST API** — CRUD endpoints with filtering, sorting, pagination -- **TypeScript DTO** — Type-safe model for Vue consumption -- **API Client** — Generated service for making API calls -- **Validation Rules** — Based on C# data annotations -- **OpenAPI/Swagger** — Full API documentation - -### Integration with Vue 3 - -```typescript -// Generated TypeScript type (auto-synced from C#) -interface Person { - personId: number; - firstName: string; - lastName: string; - dateOfBirth: Date; - companies: Company[]; + "servers": { + "coalesce": { + "command": "npx", + "args": ["coalesce-mcp@latest"] + } + } } - -// Use in Vue component with Coalesce binding -const model = ref(new Person()); ``` -## Enterprise Patterns - -### Pagination and Performance - -- Use Coalesce's built-in paging to reduce payload sizes -- Configure eager loading strategically to minimize N+1 queries -- Index frequently-filtered columns in the database +**Available tools:** -### Role-Based Access Control +- `coalesce_generate` — runs code generation (preferred over running `dotnet coalesce` directly in agent sessions) +- `get_template_features` — lists available template features and the files involved +- `read_template_file` — reads a file from the latest Coalesce template (useful during upgrades) +- `read_changelog` — reads the changelog, optionally filtered to versions newer than your current one -- Use Coalesce `[Restrict]` attributes for property-level authorization -- Implement service methods for complex authorization logic -- Validate permissions on the server side (never on the client) +**Available prompts:** -### Real-Time Updates +- `upgrade` — step-by-step agent-guided Coalesce upgrade (run via `/mcp` in VS Code chat) -- Leverage SignalR for live notifications -- Combine with Coalesce services for efficient data synchronization -- Use client-side caching to minimize round trips +**Available resources:** -### Error Handling +- `coalesce://template-file/{filePath}` — add individual template files as context. In VS Code: _Add Context... → MCP Resources... → Coalesce_ -- Standardize API responses with Coalesce validation -- Use consistent HTTP status codes -- Provide meaningful error messages to the client +Full MCP docs: https://coalesce.intellitect.com/topics/mcp-server.html -## Version History +## Documentation -### v1.0.0 (2025) +- [Coalesce Docs](https://coalesce.intellitect.com/) +- [Getting Started (Vue)](https://coalesce.intellitect.com/stacks/vue/getting-started.html) +- [coalesce.json Reference](https://coalesce.intellitect.com/topics/coalesce-json.html) +- [Coalesce GitHub](https://github.com/IntelliTect/Coalesce) -- Initial release -- Full-stack Coalesce development workflows -- EF Core patterns and best practices -- Code generation and TypeScript synchronization -- Vue 3 and Vuetify integration examples - -## Support and Feedback - -For issues, questions, or suggestions: - -- Open an issue on [GitHub](https://github.com/IntelliTect/IntelliPlugins/issues) -- Reference this plugin: `coalesce-accelerator` -- Include your Coalesce version and .NET version - -## License - -MIT License — See LICENSE file for details. - ---- +## Related Plugins -**Last Updated:** 2025 -**Maintained by:** IntelliTect Development Team +- **[C# Best Practices](https://intellitect.github.io/IntelliPlugins/plugins/csharp-best-practices)** +- **[Vuetify Components](https://intellitect.github.io/IntelliPlugins/plugins/vuetify-components)** +- **[Testing Essentials](https://intellitect.github.io/IntelliPlugins/plugins/testing-essentials)** diff --git a/plugins/coalesce-accelerator/plugin.json b/plugins/coalesce-accelerator/plugin.json index fad5e81..a22f27c 100644 --- a/plugins/coalesce-accelerator/plugin.json +++ b/plugins/coalesce-accelerator/plugin.json @@ -2,7 +2,7 @@ "name": "coalesce-accelerator", "displayName": "Coalesce Accelerator", "description": "Streamline full-stack Coalesce framework development with code generation, data binding, and EF Core migration workflows.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/coalesce-accelerator", @@ -21,9 +21,6 @@ "Vuetify", "TypeScript" ], - "categories": [ - "Framework", - "FullStack" - ], + "categories": ["Framework", "FullStack"], "skills": "skills/" } diff --git a/plugins/coalesce-accelerator/skills/add-data-source/SKILL.md b/plugins/coalesce-accelerator/skills/add-data-source/SKILL.md index 00b1943..f847140 100644 --- a/plugins/coalesce-accelerator/skills/add-data-source/SKILL.md +++ b/plugins/coalesce-accelerator/skills/add-data-source/SKILL.md @@ -40,9 +40,9 @@ public class ActiveEntitySource : StandardDataSource 2. **Decorate with `[Coalesce]`** so the CLI picks it up during code generation 3. **Override `GetQuery`** — compose from `base.GetQuery()` to retain default filtering/sorting support 4. **Optionally override `TransformResults`** for post-query shaping -5. **Run `coalesce_generate`** to expose it in the API: +5. **Run `dotnet coalesce`** to expose it in the API: ```bash - coalesce_generate + dotnet coalesce ``` 6. **Verify the new data source appears** in the generated API and TypeScript service 7. **Write tests** that confirm filtering/scoping works correctly diff --git a/plugins/coalesce-accelerator/skills/generate-migration/SKILL.md b/plugins/coalesce-accelerator/skills/generate-migration/SKILL.md index 91aa3fc..69e315b 100644 --- a/plugins/coalesce-accelerator/skills/generate-migration/SKILL.md +++ b/plugins/coalesce-accelerator/skills/generate-migration/SKILL.md @@ -25,7 +25,7 @@ Invoke this skill after you have: 2. **Regenerate Coalesce artifacts** to keep the API and TypeScript types in sync: ```bash - coalesce_generate + dotnet coalesce ``` 3. **Add the EF Core migration** using a descriptive PascalCase name that describes the change: diff --git a/plugins/coalesce-accelerator/skills/run-code-generation/SKILL.md b/plugins/coalesce-accelerator/skills/run-code-generation/SKILL.md index 06eee9a..f291666 100644 --- a/plugins/coalesce-accelerator/skills/run-code-generation/SKILL.md +++ b/plugins/coalesce-accelerator/skills/run-code-generation/SKILL.md @@ -10,823 +10,67 @@ Trigger and manage Coalesce's code generation pipeline to regenerate TypeScript ## When to Use Invoke this skill when you: + - Have made changes to C# entity classes and need to regenerate TypeScript types -- Need to run coalesce_generate and understand what it produces +- Need to run `dotnet coalesce` and understand what it produces - Want to troubleshoot code generation errors or stale generated files - Are setting up or configuring the code generation pipeline -# Coalesce Code Generation - -This guide explains what Coalesce generates, how to run code generation, customize the output, and manage regeneration workflows. - -## Overview: What Coalesce Generates - -When you run `coalesce_generate`, Coalesce inspects your C# models and automatically creates: - -### 1. REST API Endpoints - -For each model, Coalesce generates complete CRUD endpoints: - -**Model:** -```csharp -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public int CompanyId { get; set; } - public Company Company { get; set; } -} -``` - -**Generated API Endpoints:** -``` -GET /api/Person # List with filtering, paging -GET /api/Person/{id} # Get single record -POST /api/Person # Create new record -PUT /api/Person/{id} # Update record -DELETE /api/Person/{id} # Delete record -GET /api/Person/count # Count records -POST /api/Person/bulkSave # Batch create/update -``` - -### 2. TypeScript Type Definitions - -Automatic TypeScript models synchronized with C# entities: - -**Generated TypeScript (from C# model above):** -```typescript -// models/person.ts -export class Person { - public personId: number = null!; - public firstName: string = null!; - public lastName: string = null!; - public companyId: number = null!; - public company: Company | null = null; - - constructor(init?: Partial) { - Object.assign(this, init); - } -} - -export namespace Person { - export const displayName = "Person"; - export const displayPluralName = "People"; - export const singularDisplayName = "Person"; - - export const props = { - personId: { type: "number", role: "primaryKey" }, - firstName: { type: "string" }, - lastName: { type: "string" }, - companyId: { type: "number" }, - company: { type: "Company", navigationProperty: true } - }; -} -``` - -### 3. API Client Service - -Type-safe service for making API calls from Vue: - -**Generated Service (api-client/person.ts):** -```typescript -export namespace PersonService { - // List records with filtering and paging - export async function list( - filter?: ListFilters, - queryParams?: ApiQueryParams - ): Promise> { - // Implementation - } - - // Get single record - export async function get( - id: number, - queryParams?: ApiQueryParams - ): Promise> { - // Implementation - } - - // Create or update - export async function save( - item: Person - ): Promise> { - // Implementation - } - - // Delete - export async function delete( - id: number - ): Promise> { - // Implementation - } - - // Count records - export async function count( - filter?: ListFilters, - queryParams?: ApiQueryParams - ): Promise> { - // Implementation - } -} -``` - -### 4. Validation Rules - -Data annotations are converted to TypeScript validation metadata: - -**C# Model:** -```csharp -public class Person -{ - [Required] - [StringLength(100)] - public string FirstName { get; set; } - - [EmailAddress] - public string Email { get; set; } - - [Range(18, 150)] - public int Age { get; set; } -} -``` - -**Generated Validation Metadata:** -```typescript -Person.props.firstName = { - type: "string", - required: true, - maxLength: 100 -}; - -Person.props.email = { - type: "string", - pattern: "email" -}; - -Person.props.age = { - type: "number", - min: 18, - max: 150 -}; -``` - -### 5. OpenAPI/Swagger Documentation - -Auto-generated API documentation: - -``` -http://localhost:5000/swagger/index.html -``` - -Includes: -- All endpoints with request/response schemas -- Parameter descriptions from C# attributes -- Try-it-out functionality to test endpoints -- Authentication/authorization info - ## Running Code Generation -### Installation - -Ensure the Coalesce code generation tool is installed: - -```bash -dotnet tool install -g IntelliTect.Coalesce.CodeGeneration -``` - -Or add to project: -```bash -dotnet add package IntelliTect.Coalesce.CodeGeneration -``` - -### Basic Execution - -From your project directory: - ```bash -coalesce_generate +dotnet coalesce ``` -The command: -1. Reads your C# models from the project -2. Connects to the configured database to understand the schema -3. Generates TypeScript types in `wwwroot/coalesce/models/` -4. Generates API client in `wwwroot/coalesce/api-client/` -5. Generates metadata in `wwwroot/coalesce/generated.ts` +Run this from any directory at or below where `coalesce.json` lives — the tool walks up the directory tree to find it. For projects from the Coalesce template, run from the `*.Web` project directory. -### Configuration +## What Gets Generated -The tool looks for `coalesce.json` in the project root: +- `models.g.ts` — TypeScript model classes mirroring your C# entities +- `viewmodels.g.ts` — ViewModel classes with reactive state and API call methods +- `api-clients.g.ts` — Low-level typed API client classes +- `metadata.g.ts` — Property metadata (types, validation rules, display names) +- C# controller and DTO files in the web project -```json -{ - "Source": "MyApp.Web.csproj", - "OutputPath": "wwwroot/coalesce", - "TypescriptOutputPath": "wwwroot/coalesce", - "IncludeMetadata": true, - "GenerateImplementation": true -} -``` +Coalesce reads your C# assembly — it does **not** require a live database connection to generate code. -### Connection String +## Configuration (`coalesce.json`) -The generation tool needs database access. Configure in `appsettings.json`: +Place `coalesce.json` in the solution root ([full reference](https://coalesce.intellitect.com/topics/coalesce-json.html)): ```json { - "ConnectionStrings": { - "DefaultConnection": "Server=.;Database=MyAppDb;Trusted_Connection=true;" + "webProject": { + "projectFile": "src/MyApp.Web/MyApp.Web.csproj" + }, + "dataProject": { + "projectFile": "src/MyApp.Domain/MyApp.Domain.csproj" } } ``` -For development: -```bash -# Ensure database is created and migrations applied -dotnet ef database update - -# Then generate -coalesce_generate -``` - -### Troubleshooting Generation - -**Problem: "Could not find DbContext"** -```bash -# Ensure DbContext is in the assembly being scanned -# Verify DbContext is registered in DI - -# Re-run explicitly -coalesce_generate --verbose -``` - -**Problem: "Database connection failed"** -```bash -# Check connection string -# Verify database server is running -# Ensure user has connection permissions - -dotnet ef database info # Test connection -``` - -**Problem: "Generated files not updated"** -```bash -# Rebuild project first -dotnet clean -dotnet build - -# Force regenerate -rm -r wwwroot/coalesce/ -coalesce_generate -``` - -## Generated File Structure - -After running `coalesce_generate`: - -``` -wwwroot/ -└── coalesce/ - ├── models/ - │ ├── person.ts # TypeScript model class - │ ├── person.d.ts # Type definitions - │ ├── company.ts - │ ├── company.d.ts - │ └── index.ts # Barrel export - │ - ├── api-client/ - │ ├── person.ts # PersonService - │ ├── company.ts # CompanyService - │ └── index.ts # Barrel export - │ - ├── generated.ts # Master export file - ├── generated.d.ts # Type definitions - ├── metadata.g.ts # Coalesce metadata - └── README.md # Documentation -``` - -### Importing Generated Code - -**In Vue components:** - -```typescript -import { Person, Company } from '@/coalesce/generated'; -import { PersonService, CompanyService } from '@/coalesce/generated'; - -export default { - setup() { - const people = ref([]); - - onMounted(async () => { - const result = await PersonService.list(); - people.value = result.object || []; - }); - - return { people }; - } -}; -``` - -**Or import directly:** - -```typescript -import { Person } from '@/coalesce/models/person'; -import { PersonService } from '@/coalesce/api-client/person'; -``` - -## TypeScript Type Definitions - -### Model Classes - -Generated models are TypeScript classes with property definitions: - -```typescript -export class Person { - // Properties initialized to null (non-nullable in strict mode) - public personId: number = null!; - public firstName: string = null!; - public lastName: string = null!; - public dateOfBirth: Date | null = null; - public email: string | null = null; - - // Constructor for easy instantiation - constructor(init?: Partial) { - Object.assign(this, init); - } -} -``` - -### Using Generated Types - -```typescript -// Create instance -const person = new Person(); -person.firstName = "John"; -person.lastName = "Doe"; - -// Create with initialization -const person2 = new Person({ - firstName: "Jane", - lastName: "Smith", - email: "jane@example.com" -}); - -// In Vue components (fully type-safe) -const model = ref(new Person()); -model.value.firstName = "Test"; // Type-safe! -``` - -### Metadata and Introspection - -Each model includes metadata for advanced scenarios: - -```typescript -Person.displayName; // "Person" -Person.displayPluralName; // "People" -Person.singularDisplayName; // "Person" - -Person.props.personId; // Property metadata -Person.props.personId.type; // "number" -Person.props.personId.role; // "primaryKey" - -Person.props.firstName; -Person.props.firstName.type; // "string" -Person.props.firstName.required; // true -Person.props.firstName.maxLength;// 100 -``` - -## Generated API Endpoints - -### List Endpoint - -**Endpoint:** `GET /api/Person` - -**Query Parameters:** -```typescript -// Filtering -?filter={"firstName":"John"} - -// Paging -?pageSize=50&pageNumber=1 - -// Sorting -?orderBy=firstName,-companyId - -// Includes (eager load related data) -?includes=company - -// Search -?search=john -``` - -**Usage in Vue:** -```typescript -// Simple list -const result = await PersonService.list(); -console.log(result.object); // Person[] - -// With filtering -const result = await PersonService.list({ - firstName: "John" -}); - -// With paging -const result = await PersonService.list(null, { - pageSize: 20, - pageNumber: 2 -}); - -// With includes -const result = await PersonService.list(null, { - includes: "company" -}); -``` - -### Get Endpoint - -**Endpoint:** `GET /api/Person/{id}` - -**Usage:** -```typescript -const result = await PersonService.get(1); -if (result.wasSuccessful) { - console.log(result.object); // Single Person object -} -``` - -### Create/Update Endpoint - -**Endpoint:** `POST /api/Person` (create) or `PUT /api/Person/{id}` (update) - -**Usage:** -```typescript -const person = new Person({ - firstName: "John", - lastName: "Doe", - email: "john@example.com" -}); - -const result = await PersonService.save(person); - -if (result.wasSuccessful) { - console.log(result.object); // Updated Person with ID -} else { - console.log(result.errors); // Validation errors -} -``` - -### Delete Endpoint - -**Endpoint:** `DELETE /api/Person/{id}` - -**Usage:** -```typescript -const result = await PersonService.delete(1); - -if (result.wasSuccessful) { - console.log("Person deleted"); -} else { - console.log("Delete failed", result.errors); -} -``` - -### Count Endpoint - -**Endpoint:** `GET /api/Person/count` - -**Usage:** -```typescript -const result = await PersonService.count(); -console.log(result.object); // Total count - -// With filter -const result = await PersonService.count({ - companyId: 5 -}); -console.log(result.object); // Filtered count -``` - -## Regeneration Workflow - -### When to Regenerate - -Run `coalesce_generate` after: -1. **Adding a new model** — Create the C# class, add DbSet, regenerate -2. **Modifying model properties** — Change property names, types, validation, regenerate -3. **Changing relationships** — Add/remove navigation properties, regenerate -4. **Updating validation attributes** — Add/change [Required], [StringLength], etc., regenerate -5. **Database schema changes** — After running migrations, regenerate - -### Complete Regeneration Workflow - -```plaintext -1. Modify C# model - └─ Add properties, relationships, validation attributes - -2. Create EF Core migration (if database schema changed) - └─ dotnet ef migrations add MyMigrationName - -3. Apply migration - └─ dotnet ef database update - -4. Rebuild project - └─ dotnet build - -5. Regenerate Coalesce - └─ coalesce_generate - -6. Rebuild TypeScript - └─ npm run build - -7. Test in Vue component - └─ Verify new types and properties are available -``` - -### Example: Adding a Property - -**Step 1: Modify Model** -```csharp -public class Person -{ - // ... existing properties ... - - [Phone] // Add new property - public string PhoneNumber { get; set; } -} -``` - -**Step 2: Create Migration** -```bash -dotnet ef migrations add AddPhoneNumberToPerson -dotnet ef database update -``` - -**Step 3: Regenerate** -```bash -dotnet build -coalesce_generate -``` - -**Step 4: Use in Vue** -```typescript -// TypeScript now knows about phoneNumber -const person = new Person(); -person.phoneNumber = "555-1234"; - -// API includes the property -const result = await PersonService.get(1); -console.log(result.object?.phoneNumber); -``` - -## Customizing Code Generation - -### Excluding Properties from API - -Use `[Bind(false)]` to hide internal properties: - -```csharp -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - - // This won't appear in API or generated TypeScript - [Bind(false)] - [ScaffoldColumn(false)] - public string InternalId { get; set; } -} -``` - -### Excluding Models from Generation - -Use `[Coalesce(false)]` to skip a model: - -```csharp -[Coalesce(false)] -public class InternalConfig -{ - // Not exposed via API -} -``` - -### Custom Display Names - -Control how properties appear in API docs and UI: - -```csharp -public class Person -{ - [Display(Name = "First Name", Order = 1)] - public string FirstName { get; set; } - - [Display(Name = "Last Name", Order = 2)] - public string LastName { get; set; } -} -``` - -### API Attributes +## When to Regenerate -Fine-tune API generation with attributes: +Run `dotnet coalesce` after: -```csharp -[Coalesce] -public class PersonService : StandardDataService -{ - // Executed before returning list results - public override IQueryable GetQuery() - { - return base.GetQuery() - .Include(p => p.Company) - .Where(p => !p.IsDeleted); - } - - // Executed before save - public override async Task> BeforeSave(SaveRequest request) - { - // Validate, authorize, etc. - return await base.BeforeSave(request); - } -} -``` - -### Bulk Operations - -Configure bulk create/update: - -```csharp -[Coalesce] -public class PersonService : StandardDataService -{ - // Configure bulk save behavior - public override async Task> BeforeSave(SaveRequest request) - { - if (request.ForceUpdateDuringCreate) - { - // Handle as update even if no ID - } - return await base.BeforeSave(request); - } -} -``` - -## Generated Validation - -### Validation Constraints - -C# validation attributes are converted to TypeScript constraints: - -**C# Model:** -```csharp -public class Person -{ - [Required(ErrorMessage = "Name is required")] - [StringLength(100, MinimumLength = 2)] - public string Name { get; set; } - - [Range(18, 150)] - public int Age { get; set; } - - [EmailAddress] - public string Email { get; set; } - - [RegularExpression(@"^\d{5}$")] - public string ZipCode { get; set; } -} -``` - -**Generated TypeScript Metadata:** -```typescript -Person.props.name = { - type: "string", - required: true, - minLength: 2, - maxLength: 100 -}; - -Person.props.age = { - type: "number", - min: 18, - max: 150 -}; - -Person.props.email = { - type: "string", - pattern: "email" -}; - -Person.props.zipCode = { - type: "string", - pattern: /^\d{5}$/ -}; -``` +1. Adding, renaming, or removing a model property +2. Adding or removing navigation properties or relationships +3. Changing `[Coalesce]` attributes, data sources, or behaviors +4. Adding a new entity class to the `DbContext` -### Using Validation in Vue - -```typescript -import { Person } from '@/coalesce/generated'; - -const person = ref(new Person()); -const errors = ref>({}); - -async function savePerson() { - // Validate before sending - if (!person.value.name) { - errors.value.name = "Name is required"; - return; - } - - if (person.value.name.length < 2) { - errors.value.name = "Name must be at least 2 characters"; - return; - } - - // Send to API - const result = await PersonService.save(person.value); - - if (!result.wasSuccessful) { - errors.value = result.errors || {}; - } -} -``` - -### Server-Side Validation - -API always validates using C# data annotations: - -```typescript -const result = await PersonService.save({ - personId: 0, - name: "X", // Too short (min 2) - age: 10, // Below range (min 18) - email: "invalid" -}); - -if (!result.wasSuccessful) { - console.log(result.errors); - // { - // name: "Name must be at least 2 characters", - // age: "Must be between 18 and 150", - // email: "Invalid email address" - // } -} -``` - -## Performance and Generation - -### Generation Performance - -For large codebases: -1. Generation reads entire assembly and database schema -2. Larger models = slower generation -3. Database connection required (adds latency) - -### Optimizing Generation - -- Exclude unrelated assemblies from scanning -- Use specific DbContext if multiple exist -- Close other tools connecting to database - -### Incremental Development - -Work locally with local database: -```bash -# Use LocalDB or SQL Express for fast generation cycles -# Connection: Server=(localdb)\mssqllocaldb;Database=MyApp; -``` - -## Troubleshooting Common Issues - -### Types Not Updating +## Troubleshooting +**Generated files not updating:** ```bash -# Clear generated files -rm -r wwwroot/coalesce/ - -# Rebuild -dotnet build - -# Regenerate -coalesce_generate +dotnet clean && dotnet build +dotnet coalesce ``` -### API Endpoints Missing - -1. Verify model has `[Coalesce]` attribute (if required) -2. Verify model is public -3. Verify DbSet exists in DbContext -4. Run `coalesce_generate` again -5. Rebuild and restart application - -### TypeScript Compilation Errors - -1. Verify `wwwroot/coalesce/generated.ts` exists -2. Check tsconfig.json includes generated files -3. Run `npm install` to ensure dependencies -4. Clear node_modules and reinstall if needed - -## Next Steps +**API endpoints missing:** +1. Verify the entity's `DbSet` exists in `DbContext` +2. Verify the model is `public` +3. Re-run `dotnet coalesce` and restart the app -- Review **Coalesce Workflows** for full development cycle -- Study **EF Core Patterns** for advanced model design -- Explore official samples at [https://coalesce.intellitect.com/](https://coalesce.intellitect.com/) +**TypeScript compilation errors:** +1. Verify `models.g.ts` and `viewmodels.g.ts` exist in the Vue `src/` folder +2. Run `npm install` to ensure dependencies are current diff --git a/plugins/coalesce-accelerator/skills/scaffold-entity/SKILL.md b/plugins/coalesce-accelerator/skills/scaffold-entity/SKILL.md index 6e72a4a..24e74e7 100644 --- a/plugins/coalesce-accelerator/skills/scaffold-entity/SKILL.md +++ b/plugins/coalesce-accelerator/skills/scaffold-entity/SKILL.md @@ -10,6 +10,7 @@ Create a complete Coalesce-compatible C# entity class with appropriate EF Core d ## When to Use Invoke this skill when you need to: + - Add a new domain entity to the Coalesce data model - Create a related/child entity with a foreign key relationship - Scaffold a lookup/reference table entity @@ -17,6 +18,7 @@ Invoke this skill when you need to: ## Required Information Before scaffolding, gather: + 1. **Entity name** — PascalCase noun (e.g., `Invoice`, `ProjectMilestone`) 2. **Properties** — names, types, and whether required/optional 3. **Relationships** — parent entity (if any), one-to-many or many-to-many @@ -63,9 +65,9 @@ public class EntityName ```csharp public DbSet EntityNames { get; set; } ``` -3. **Run `coalesce_generate`** to generate the API and TypeScript types: +3. **Run `dotnet coalesce`** to generate the API and TypeScript types: ```bash - coalesce_generate + dotnet coalesce ``` 4. **Create a migration** to add the table (use the `generate-migration` skill) 5. **Build and test**: @@ -75,768 +77,11 @@ public class EntityName ## Coalesce-Specific Annotations -| Attribute | Effect | -|-----------|--------| -| `[Read(Roles = "Admin")]` | Restrict read access by role | -| `[Edit(Roles = "Admin")]` | Restrict edit access by role | -| `[Hidden]` | Exclude from generated list views | -| `[Search]` | Include property in text search | -| `[DefaultOrderBy]` | Set default sort property | -| `[ListText]` | Use as display text in dropdowns | - - -## Reference: EF Core Patterns - -# Entity Framework Core Patterns with Coalesce - -This guide covers Entity Framework Core (EF Core) best practices, patterns, and configurations specifically for Coalesce development. - -## Model Design for Coalesce - -### Basic Entity Structure - -Coalesce entities should follow this pattern: - -```csharp -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -[Table("Companies")] -public class Company -{ - [Key] - public int CompanyId { get; set; } - - [Required(ErrorMessage = "Company name is required")] - [StringLength(255, ErrorMessage = "Name cannot exceed 255 characters")] - [Display(Name = "Company Name")] - public string Name { get; set; } - - [EmailAddress(ErrorMessage = "Invalid email address")] - public string Email { get; set; } - - [Phone] - public string PhoneNumber { get; set; } - - [Range(typeof(DateTime), "1/1/2000", "1/1/2100")] - public DateTime? FoundedDate { get; set; } - - // Navigation property - public ICollection Employees { get; set; } = new List(); - - // Metadata - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedAt { get; set; } -} -``` - -### Key Conventions - -1. **Primary Key** — Name property `{EntityName}Id` (e.g., `CompanyId`, `PersonId`) -2. **Required Properties** — Use `[Required]` attribute -3. **String Length** — Use `[StringLength]` to define database column size -4. **Validation** — Use data annotation attributes for validation -5. **Display Names** — Use `[Display]` for UI labels in generated views -6. **Navigation Properties** — Use `ICollection` for collections - -## Relationship Patterns - -### One-to-Many Relationship - -**Most common pattern** — Parent has multiple children. - -**Models:** -```csharp -public class Department -{ - public int DepartmentId { get; set; } - public string Name { get; set; } - public ICollection Employees { get; set; } = new List(); -} - -public class Employee -{ - public int EmployeeId { get; set; } - public string Name { get; set; } - - // Foreign key - public int DepartmentId { get; set; } - - // Navigation property - public Department Department { get; set; } -} -``` - -**DbContext Configuration:** -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - modelBuilder.Entity() - .HasOne(e => e.Department) - .WithMany(d => d.Employees) - .HasForeignKey(e => e.DepartmentId) - .OnDelete(DeleteBehavior.Restrict); // Prevent orphans -} -``` - -**API Generated:** -- GET `/api/Department/{id}` — Returns department with embedded employees -- GET `/api/Employee?DepartmentId={id}` — Filter by department -- POST `/api/Department` — Create with nested employees - -**Vue Usage:** -```typescript -import { Department } from '@/coalesce/models'; -import { DepartmentService } from '@/coalesce/api-client'; - -const department = ref(); - -onMounted(async () => { - const result = await DepartmentService.get(1); - if (result.wasSuccessful) { - department.value = result.object; - } -}); -``` - -### Many-to-Many Relationship - -**Pattern** — Two entities with independent collections. - -**Example:** Students and Courses (each student can take multiple courses, each course has multiple students). - -**Models:** -```csharp -public class Student -{ - public int StudentId { get; set; } - public string Name { get; set; } - public ICollection StudentCourses { get; set; } = new List(); -} - -public class Course -{ - public int CourseId { get; set; } - public string Title { get; set; } - public ICollection StudentCourses { get; set; } = new List(); -} - -// Junction table (bridge entity) -public class StudentCourse -{ - public int StudentId { get; set; } - public Student Student { get; set; } - - public int CourseId { get; set; } - public Course Course { get; set; } - - // Additional properties for the relationship - public DateTime EnrolledDate { get; set; } - public decimal? Grade { get; set; } -} -``` - -**DbContext Configuration:** -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - // Define composite primary key - modelBuilder.Entity() - .HasKey(sc => new { sc.StudentId, sc.CourseId }); - - // Foreign key relationships - modelBuilder.Entity() - .HasOne(sc => sc.Student) - .WithMany(s => s.StudentCourses) - .HasForeignKey(sc => sc.StudentId); - - modelBuilder.Entity() - .HasOne(sc => sc.Course) - .WithMany(c => c.StudentCourses) - .HasForeignKey(sc => sc.CourseId); -} -``` - -**Vue Usage:** -```typescript -// Load student with enrolled courses -const student = ref(); -student.value = await StudentService.get(1); - -// Iterate through courses -student.value.studentCourses?.forEach(enrollment => { - console.log(`${enrollment.course?.title}: ${enrollment.grade}`); -}); -``` - -### Self-Referential Relationship - -**Pattern** — Entity references itself (e.g., manager/employee, parent/child comments). - -**Model:** -```csharp -public class Employee -{ - public int EmployeeId { get; set; } - public string Name { get; set; } - - // Manager reference (nullable - CEO has no manager) - public int? ManagerId { get; set; } - public Employee Manager { get; set; } - - // Direct reports - public ICollection DirectReports { get; set; } = new List(); -} -``` - -**DbContext Configuration:** -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - modelBuilder.Entity() - .HasOne(e => e.Manager) - .WithMany(e => e.DirectReports) - .HasForeignKey(e => e.ManagerId) - .OnDelete(DeleteBehavior.Restrict); -} -``` - -**Vue Usage:** -```typescript -// Load organizational hierarchy -const ceo = await EmployeeService.get(1); -ceo.directReports?.forEach(report => { - console.log(`${report.name} reports to ${ceo.name}`); -}); -``` - -## Data Annotations and Configurations - -### Validation Attributes - -```csharp -public class Product -{ - [Required(ErrorMessage = "Product name is required")] - public string Name { get; set; } - - [Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than 0")] - public decimal Price { get; set; } - - [StringLength(500, MinimumLength = 10)] - public string Description { get; set; } - - [EmailAddress] - public string ContactEmail { get; set; } - - [Phone] - public string ContactPhone { get; set; } - - [RegularExpression(@"^\d{5}-\d{4}$", ErrorMessage = "ZIP code must be in format XXXXX-XXXX")] - public string ZipCode { get; set; } -} -``` - -Coalesce automatically: -- Adds validation to generated API -- Generates TypeScript validation rules -- Displays validation errors in UI forms - -### Display and Metadata Attributes - -```csharp -public class Product -{ - [Display(Name = "Product ID", Order = 1)] - public int ProductId { get; set; } - - [Display(Name = "Product Name", Order = 2, Description = "The name of the product")] - public string Name { get; set; } - - [Display(Name = "List Price", Order = 3)] - [DisplayFormat(DataFormatString = "{0:C}")] - public decimal Price { get; set; } - - [Display(Name = "Available", Order = 4)] - public bool IsAvailable { get; set; } -} -``` - -### Binding Control - -```csharp -public class User -{ - public int UserId { get; set; } - - [Required] - public string Email { get; set; } - - // Exclude from API/binding - [Bind(false)] - [ScaffoldColumn(false)] - public string PasswordHash { get; set; } - - // Exclude large collection from API responses - [Bind(false)] - public ICollection AuditLogs { get; set; } -} -``` - -## Lazy Loading vs Eager Loading vs Explicit Loading - -### Lazy Loading (Default) - -Related data is loaded only when accessed: - -```csharp -// Lazy loading disabled by default in EF Core -var employee = await _context.Employees.FirstAsync(e => e.EmployeeId == 1); -// Department not loaded yet - -var department = employee.Department; // LAZY LOAD - Database call here -``` - -**Pros:** Only load data when needed; less memory overhead -**Cons:** N+1 query problem if not careful; unpredictable performance - -### Eager Loading (Recommended for Coalesce) - -Related data is loaded immediately with `.Include()`: - -```csharp -// Load employee with related department -var employee = await _context.Employees - .Include(e => e.Department) - .FirstAsync(e => e.EmployeeId == 1); - -var department = employee.Department; // Already loaded -``` - -**Pros:** Predictable, single query; prevents N+1 problems -**Cons:** May load unnecessary data - -**In Coalesce services:** -```csharp -[Coalesce] -public class EmployeeService : StandardDataService -{ - public override IQueryable GetQuery() - { - return base.GetQuery() - .Include(e => e.Department) - .Include(e => e.Manager); - } -} -``` - -### Explicit Loading - -Load related data on-demand: - -```csharp -var employee = await _context.Employees.FirstAsync(e => e.EmployeeId == 1); - -// Explicitly load department later -await _context.Entry(employee) - .Reference(e => e.Department) - .LoadAsync(); -``` - -**Pros:** Flexible; load different data based on context -**Cons:** Multiple database queries; requires careful orchestration - -## Validation with Coalesce - -### Model-Level Validation - -Use data annotations for automatic validation: - -```csharp -public class Order -{ - [Required] - public int OrderId { get; set; } - - [Required(ErrorMessage = "Customer is required")] - public int CustomerId { get; set; } - - [Range(0.01, double.MaxValue)] - public decimal TotalAmount { get; set; } - - [Range(typeof(DateTime), "1/1/2020", "1/1/2100")] - public DateTime OrderDate { get; set; } -} -``` - -### Custom Validation Logic - -Implement `IValidatableObject` for complex rules: - -```csharp -public class Order : IValidatableObject -{ - public int OrderId { get; set; } - public int CustomerId { get; set; } - public decimal TotalAmount { get; set; } - public DateTime OrderDate { get; set; } - - public IEnumerable Validate(ValidationContext context) - { - if (OrderDate > DateTime.Now) - yield return new ValidationResult("Order date cannot be in the future"); - - if (TotalAmount < 10) - yield return new ValidationResult("Minimum order amount is $10"); - } -} -``` - -### Service-Level Validation - -For business logic validation: - -```csharp -[Coalesce] -public class OrderService : StandardDataService -{ - public override async Task> BeforeSave(SaveRequest request) - { - var result = await base.BeforeSave(request); - - if (!result.IsSuccessful) return result; - - var order = request.Object; - - // Check customer exists - var customer = await Db.Customers.FindAsync(order.CustomerId); - if (customer == null) - result.Errors.Add("CustomerId", "Customer not found"); - - // Check inventory - if (!await CheckInventoryAvailable(order)) - result.Errors.Add("Items", "Insufficient inventory"); - - return result; - } -} -``` - -## Migrations and Database Schema - -### Creating Migrations - -After creating or modifying models: - -```bash -# Add migration with descriptive name -dotnet ef migrations add AddOrderTable -dotnet ef migrations add AddCustomerPhoneNumber -dotnet ef migrations add CreateStudentCourseRelationship -``` - -### Migration File Structure - -```csharp -public partial class AddOrderTable : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - OrderId = table.Column(), - CustomerId = table.Column(), - TotalAmount = table.Column(), - OrderDate = table.Column(), - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.OrderId); - table.ForeignKey( - name: "FK_Orders_Customers_CustomerId", - column: x => x.CustomerId, - principalTable: "Customers", - principalColumn: "CustomerId", - onDelete: ReferentialAction.Restrict); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable(name: "Orders"); - } -} -``` - -### Applying Migrations - -```bash -# Update database to latest migration -dotnet ef database update - -# Update to specific migration -dotnet ef database update AddOrderTable - -# Revert to previous migration -dotnet ef database update PreviousMigrationName -``` - -### Best Practices - -1. **Create migrations frequently** — Don't wait to run multiple changes -2. **Use descriptive names** — `AddCustomerPhoneNumber` not `Update1` -3. **Never modify applied migrations** — Create new migration for changes -4. **Script for production** — Use `dotnet ef migrations script` for production deployments -5. **Test on staging** — Verify migrations on test database first - -## Navigation Properties and DTOs - -### Navigation Properties in Coalesce - -By default, Coalesce includes navigation properties in API responses: - -```csharp -// Model with navigation properties -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - - public int CompanyId { get; set; } - public Company Company { get; set; } // Included in API response -} - -// Generated API response -{ - "personId": 1, - "firstName": "John", - "companyId": 5, - "company": { - "companyId": 5, - "name": "ACME Corp" - } -} -``` - -### Excluding Navigation Properties - -Use `[Bind(false)]` to exclude large collections: - -```csharp -public class Company -{ - public int CompanyId { get; set; } - public string Name { get; set; } - - // Exclude large collection from responses - [Bind(false)] - public ICollection Employees { get; set; } -} -``` - -### Using DTOs for Complex Scenarios - -Create specialized models for specific use cases: - -```csharp -// Entity model -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public ICollection Skills { get; set; } -} - -// DTO for list view (minimal data) -[Coalesce] -public class PersonListDto -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } -} - -// DTO for detail view (rich data) -[Coalesce] -public class PersonDetailDto -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public ICollection Skills { get; set; } -} -``` - -## Performance Patterns - -### Indexing Strategy - -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) -{ - // Single column index - modelBuilder.Entity() - .HasIndex(p => p.Email) - .IsUnique(); - - // Composite index - modelBuilder.Entity() - .HasIndex(p => new { p.FirstName, p.LastName }) - .HasName("IX_PersonName"); - - // Include additional columns in index - modelBuilder.Entity() - .HasIndex(p => p.LastName) - .IncludeProperties(p => p.FirstName, p => p.Email); -} -``` - -### Pagination for Large Result Sets - -```csharp -[Coalesce] -public class PersonService : StandardDataService -{ - public override IQueryable GetQuery() - { - return base.GetQuery() - .Include(p => p.Company) - .OrderBy(p => p.PersonId); - } -} - -// Use in Vue -const result = await PersonService.list(null, { - pageSize: 50, - pageNumber: 1, - orderBy: "-personId" -}); -``` - -### Filtering and Searching - -```csharp -public class Person -{ - public int PersonId { get; set; } - - [Search] // Enable full-text search - public string FirstName { get; set; } - - [Search] - public string LastName { get; set; } - - [Bind] // Include in API filtering - public string Email { get; set; } -} - -// Generated API supports -// GET /api/Person?search=john&email=john@example.com -``` - -## Common Patterns and Examples - -### Soft Deletes (Logical Deletion) - -Instead of physically deleting records: - -```csharp -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - - public bool IsDeleted { get; set; } - public DateTime? DeletedAt { get; set; } -} - -[Coalesce] -public class PersonService : StandardDataService -{ - public override IQueryable GetQuery() - { - return base.GetQuery().Where(p => !p.IsDeleted); - } - - public override async Task> BeforeDelete(Person item) - { - item.IsDeleted = true; - item.DeletedAt = DateTime.UtcNow; - await Db.SaveChangesAsync(); - return new SaveResult { Object = item }; - } -} -``` - -### Audit Trail - -Track changes to entities: - -```csharp -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - - // Audit fields - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public string CreatedBy { get; set; } - - public DateTime? UpdatedAt { get; set; } - public string UpdatedBy { get; set; } -} - -public class AuditLog -{ - public int AuditLogId { get; set; } - public string EntityType { get; set; } - public int EntityId { get; set; } - public string Action { get; set; } // Created, Updated, Deleted - public string UserId { get; set; } - public DateTime Timestamp { get; set; } - public string Changes { get; set; } // JSON diff -} -``` - -## Integration with Coalesce Services - -Services extend `StandardDataService` to add business logic: - -```csharp -[Coalesce] -public class PersonService : StandardDataService -{ - private readonly IEmailService _emailService; - - public PersonService(AppDbContext db, IEmailService emailService) - : base(db) - { - _emailService = emailService; - } - - public override async Task> BeforeSave(SaveRequest request) - { - var result = await base.BeforeSave(request); - if (!result.IsSuccessful) return result; - - var person = request.Object; - - // Send welcome email for new users - if (request.WasNew) - { - await _emailService.SendWelcomeAsync(person.Email); - } - - return result; - } -} -``` - -## Next Steps - -- Study **Coalesce Workflows** for project setup and generation -- Review **Code Generation** guide for API customization -- Explore Coalesce samples at [https://coalesce.intellitect.com/](https://coalesce.intellitect.com/) +| Attribute | Effect | +| ------------------------- | --------------------------------- | +| `[Read(Roles = "Admin")]` | Restrict read access by role | +| `[Edit(Roles = "Admin")]` | Restrict edit access by role | +| `[Hidden]` | Exclude from generated list views | +| `[Search]` | Include property in text search | +| `[DefaultOrderBy]` | Set default sort property | +| `[ListText]` | Use as display text in dropdowns | diff --git a/plugins/coalesce-accelerator/skills/setup-coalesce-project/SKILL.md b/plugins/coalesce-accelerator/skills/setup-coalesce-project/SKILL.md index 26063af..c7f8479 100644 --- a/plugins/coalesce-accelerator/skills/setup-coalesce-project/SKILL.md +++ b/plugins/coalesce-accelerator/skills/setup-coalesce-project/SKILL.md @@ -10,526 +10,70 @@ Guide the full setup of a new Coalesce full-stack project including C# models, E ## When to Use Invoke this skill when you: + - Are starting a brand new Coalesce project from scratch - Need to configure an existing project to use Coalesce - Want to understand the required project structure and dependencies -- Are setting up the full development workflow for a Coalesce application - -# Coalesce Workflows - -This guide covers the end-to-end workflows for developing with Coalesce, from project setup through code generation to full-stack deployment. -## Development Workflow Overview +## Creating a New Project -Coalesce development follows a **model-first** approach: +Use the official Coalesce Vue template ([getting started docs](https://coalesce.intellitect.com/stacks/vue/getting-started.html)): +```bash +dotnet new install IntelliTect.Coalesce.Vue.Template +dotnet new coalescevue -n MyCompany.MyProject -o MyProject +cd MyProject/*.Web +npm i +dotnet restore +dotnet coalesce ``` -Design C# Models - ↓ -Configure EF Core & DbContext - ↓ -Create Database Migrations - ↓ -Run coalesce_generate - ↓ -Generated APIs & TypeScript Types - ↓ -Build Vue 3 Components - ↓ -Test End-to-End -``` - -Each change to the C# model requires regenerating Coalesce artifacts to keep the API and TypeScript types in sync. -## Setting Up a New Coalesce Project +The template includes all required configuration for ASP.NET Core, EF Core, Vue 3, and Coalesce out of the box. -### Prerequisites -- .NET 6+ SDK -- Node.js 16+ for Vue 3 frontend -- Visual Studio Code or Visual Studio -- SQL Server or another EF Core-supported database +## Adding Coalesce to an Existing Project -### Creating a New Project +1. **Add the NuGet package** to the web project: -1. **Create the solution structure:** ```bash -dotnet new sln -n MyCoalesceApp -cd MyCoalesceApp -``` - -2. **Add a web project with Coalesce templates:** -```bash -# If using Coalesce template -dotnet new coalesce-app -n MyApp.Web -# OR create manually with ASP.NET Core project -``` - -3. **Install Coalesce NuGet package:** -```bash -cd MyApp.Web dotnet add package IntelliTect.Coalesce ``` -4. **Configure the project structure:** -``` -MyApp.Web/ -├── Models/ # C# entity models -├── Data/ -│ └── AppDbContext.cs # EF Core DbContext -├── Controllers/ # API controllers (Coalesce generates many) -├── Services/ # Custom business logic -├── Migrations/ # EF Core migrations -├── wwwroot/ -│ └── coalesce/ # Generated TypeScript and API client -└── Startup.cs or Program.cs # Configure Coalesce and EF Core -``` - -### Configure Startup - -In `Program.cs`: - -```csharp -using IntelliTect.Coalesce; - -var builder = WebApplicationBuilder.CreateBuilder(args); - -// Add DbContext -builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); - -// Add Coalesce -builder.Services.AddCoalesce(typeof(Program).Assembly); - -// Add CORS if needed for development -builder.Services.AddCors(options => -{ - options.AddPolicy("CorsPolicy", builder => - builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader()); -}); - -var app = builder.Build(); - -app.UseCors("CorsPolicy"); -app.MapControllers(); - -app.Run(); -``` - -## Creating Models and DbContext - -### Model Design - -Create models in the `Models` directory: - -```csharp -using System.ComponentModel.DataAnnotations; - -public class Company -{ - public int CompanyId { get; set; } - - [Required(ErrorMessage = "Company name is required")] - [StringLength(255)] - public string Name { get; set; } - - [EmailAddress] - public string Email { get; set; } - - public string PhoneNumber { get; set; } - - // Navigation property (one-to-many) - public ICollection Employees { get; set; } = new List(); -} - -public class Person -{ - public int PersonId { get; set; } - - [Required] - [StringLength(100)] - public string FirstName { get; set; } - - [Required] - [StringLength(100)] - public string LastName { get; set; } - - public DateTime DateOfBirth { get; set; } - - [EmailAddress] - public string Email { get; set; } - - // Foreign key - public int CompanyId { get; set; } - - // Navigation property (many-to-one) - public Company Company { get; set; } -} -``` - -### DbContext Configuration - -Create `AppDbContext.cs`: +2. **Register Coalesce** in `Program.cs`: ```csharp -using Microsoft.EntityFrameworkCore; - -public class AppDbContext : DbContext -{ - public AppDbContext(DbContextOptions options) : base(options) { } - - public DbSet Companies { get; set; } - public DbSet People { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - // Configure relationships - modelBuilder.Entity() - .HasOne(p => p.Company) - .WithMany(c => c.Employees) - .HasForeignKey(p => p.CompanyId) - .OnDelete(DeleteBehavior.Restrict); - } -} +builder.Services.AddCoalesce(); ``` -## Code Generation Process - -### Understanding coalesce_generate - -The `coalesce_generate` command inspects your C# models and generates: - -- **REST API endpoints** — Automatic CRUD operations -- **Data Transfer Objects (DTOs)** — API request/response models -- **TypeScript types** — Type-safe models for Vue consumption -- **API client** — Generated service for making API calls -- **Validation rules** — Based on C# attributes -- **OpenAPI documentation** — Swagger UI integration - -### Running Code Generation +3. **Add `coalesce.json`** to the solution root pointing to your projects: -1. **Ensure the database connection is configured** in `appsettings.json`: ```json { - "ConnectionStrings": { - "DefaultConnection": "Server=.;Database=MyCoalesceAppDb;Trusted_Connection=true;" + "webProject": { + "projectFile": "src/MyApp.Web/MyApp.Web.csproj" + }, + "dataProject": { + "projectFile": "src/MyApp.Domain/MyApp.Domain.csproj" } } ``` -2. **Create initial migration** (if database is new): -```bash -dotnet ef migrations add InitialCreate -dotnet ef database update -``` +4. **Run code generation** (use the `run-code-generation` skill): -3. **Run Coalesce code generation:** ```bash -# From the project directory -coalesce_generate - -# Or if coalesce CLI is not installed globally: -dotnet tool install -g IntelliTect.Coalesce.CodeGeneration -coalesce_generate -``` - -The command will: -- Read models from the project -- Connect to the database (to understand schema) -- Generate TypeScript types in `wwwroot/coalesce/models` -- Generate API client in `wwwroot/coalesce/api-client` -- Generate metadata for code generation - -### Generated File Structure - -After running `coalesce_generate`: - +dotnet coalesce ``` -wwwroot/coalesce/ -├── models/ -│ ├── company.ts # TypeScript model -│ ├── company.d.ts # Type definitions -│ └── person.ts -├── api-client/ -│ ├── company.ts # Generated API service -│ ├── person.ts -│ └── index.ts -├── metadata.g.ts # Coalesce metadata -└── generated.ts # Re-export of all generated types -``` - -## TypeScript Type Synchronization -### Automatic Synchronization +## Model and DbContext Pattern -TypeScript types are **automatically generated from C# models** when you run `coalesce_generate`. This ensures type safety across your full stack. - -### Example Sync - -**C# Model:** -```csharp -public class Person -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public DateTime DateOfBirth { get; set; } -} -``` - -**Generated TypeScript:** -```typescript -// models/person.ts -export class Person { - public personId: number = null!; - public firstName: string = null!; - public dateOfBirth: Date = null!; -} -``` - -**Accessing in Vue:** -```typescript -import { Person } from '@/coalesce/models'; - -const person = ref(new Person()); -person.value.firstName = "John"; // Type-safe! -``` - -### Re-generating After Model Changes - -Workflow for adding a property: - -1. **Add property to C# model:** ```csharp -[Required] -[StringLength(20)] -public string PhoneNumber { get; set; } -``` - -2. **Create migration (if schema changed):** -```bash -dotnet ef migrations add AddPhoneNumber -dotnet ef database update -``` - -3. **Regenerate Coalesce:** -```bash -coalesce_generate -``` - -4. **Verify TypeScript types updated:** -```typescript -// Build should succeed with new property -const person = new Person(); -person.phoneNumber = "555-1234"; -``` - -## Common Workflow Patterns - -### Pattern 1: Adding a New Entity - -```plaintext -1. Create C# model with properties and validation -2. Add DbSet to AppDbContext -3. Create EF Core migration -4. Run coalesce_generate -5. Create Vue component for the entity -6. Wire up list view and forms with generated API client -7. Test CRUD operations end-to-end -``` - -### Pattern 2: Implementing Complex Filtering - -```plaintext -1. Add filter properties to model (with [Bind] attribute) -2. Optionally add [Search] for full-text search -3. Regenerate Coalesce -4. Use generated filters in Vue list components -5. Test pagination and filtering performance -``` - -### Pattern 3: Implementing Role-Based Access - -```plaintext -1. Add [Restrict] attributes to model/properties -2. Implement authorization in custom service methods -3. Regenerate to apply restrictions to API -4. Test access control through UI -``` - -### Pattern 4: Working with Complex Relationships - -```plaintext -1. Design entities and relationships (1-M, M-M) -2. Configure in DbContext OnModelCreating -3. Create migrations with proper foreign keys -4. Regenerate Coalesce -5. Load related data using include/expand in Vue -6. Bind nested collections in UI -``` - -## Troubleshooting Code Generation - -### Issue: "coalesce_generate not found" - -**Solution:** Install the Coalesce code generation tool: -```bash -dotnet tool install -g IntelliTect.Coalesce.CodeGeneration -``` - -Or run via dotnet: -```bash -dotnet tool run coalesce_generate -``` - -### Issue: Database Connection Failed - -**Check:** -1. Connection string in `appsettings.json` is correct -2. Database server is running -3. User has permissions to connect -4. Migrations have been applied (`dotnet ef database update`) - -**Solution:** -```bash -# Verify connection -dotnet ef database info - -# Update database to latest migration -dotnet ef database update -``` - -### Issue: Generated Types Don't Match Model - -**Check:** -1. Run `coalesce_generate` again after model changes -2. Ensure migrations were applied to the database -3. Check for uncommitted changes in the project - -**Solution:** -```bash -# Rebuild and regenerate -dotnet clean -dotnet build -coalesce_generate -``` - -### Issue: TypeScript Compilation Errors - -**Check:** -1. Verify generated files are in correct location (`wwwroot/coalesce/`) -2. Check tsconfig.json paths are configured correctly -3. Ensure all dependencies are installed (`npm install`) - -**Solution:** -```bash -# Reinstall dependencies -npm install - -# Clear generated cache -rm -rf wwwroot/coalesce/ - -# Regenerate -coalesce_generate - -# Rebuild TypeScript -npm run build -``` - -## Best Practices for Model Design - -### 1. Use Meaningful Names -- Model: `Person`, `Company`, `Order` (singular nouns) -- Properties: `FirstName`, `LastName`, `DateOfBirth` (PascalCase) -- Foreign Keys: `CompanyId` (Entity + Id) - -### 2. Include Validation Attributes -```csharp -[Required] -[StringLength(100)] -[EmailAddress] -public string Email { get; set; } -``` - -Validation attributes are automatically converted to TypeScript constraints and API validation. - -### 3. Design for Discoverability -Coalesce generates APIs for all public properties. Hide internal properties: -```csharp -[ApiExplorerSettings(IgnoreApi = true)] -public string InternalField { get; set; } -``` - -### 4. Plan Navigation Properties Carefully -- Use `ICollection` for one-to-many relationships -- Consider lazy loading implications -- Use `[Bind(false)]` to exclude large collections from API responses - -### 5. Version Your API -If making breaking changes, version the API: -```csharp -[ApiController] -[Route("api/v1/[controller]")] -public class PersonController : ControllerBase { } -``` - -### 6. Use DTOs for Sensitive Data -For properties that shouldn't be exposed via API, create a DTO: -```csharp -public class PersonDto -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - // Exclude sensitive properties -} -``` - -## Performance Considerations - -### 1. Eager Load Related Data -```csharp -public IQueryable GetPeople() -{ - return _context.People.Include(p => p.Company); -} -``` - -### 2. Paginate Large Result Sets -Coalesce supports built-in paging: -```typescript -// Generated API client -const result = await PersonService.list(null, { - pageSize: 20, - pageNumber: 1 -}); -``` - -### 3. Optimize Database Indexes -```csharp -protected override void OnModelCreating(ModelBuilder modelBuilder) +// AppDbContext.cs +public class AppDbContext : DbContext { - modelBuilder.Entity() - .HasIndex(p => p.Email) - .IsUnique(); -} -``` + public AppDbContext(DbContextOptions options) : base(options) { } -### 4. Use Projections for Large Models -```csharp -[Coalesce] -public class PersonListDto -{ - public int PersonId { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - // Exclude large properties for list view + public DbSet Companies { get; set; } + public DbSet People { get; set; } } ``` -## Next Steps - -- Review **EF Core Patterns** guide for advanced model design -- Study **Code Generation** guide for customization options -- Explore Coalesce samples at [https://coalesce.intellitect.com/](https://coalesce.intellitect.com/) +See the `scaffold-entity` skill for the entity class pattern. diff --git a/plugins/csharp-best-practices/README.md b/plugins/csharp-best-practices/README.md index 8be87d6..240d04e 100644 --- a/plugins/csharp-best-practices/README.md +++ b/plugins/csharp-best-practices/README.md @@ -4,15 +4,15 @@ Professional C#-specific language patterns, naming conventions, async patterns, ## Overview -C# is a modern, enterprise-grade programming language that combines the power of C++ with the simplicity of Visual Basic. This plugin provides comprehensive guidance on writing idiomatic, maintainable, and efficient C# code aligned with industry standards and Microsoft best practices. +C# is a cross-platform, modern, enterprise-grade programming language. This plugin provides comprehensive guidance on writing idiomatic, maintainable, and efficient C# code aligned with industry standards and Microsoft best practices. Whether you're building console applications, web services, desktop clients, or cloud-native systems, this plugin helps you leverage C# language features effectively and write code that scales with your team. ## What's Covered ### 1. **C# Language Patterns** (`csharp-patterns.md`) + - **Type Design & Organization** - Classes, records, interfaces, and enums -- **Naming Conventions** - Methods, properties, fields, and constants - **Property Patterns** - Auto-properties, init-only properties, and property initialization - **Null Handling** - Null coalescing operators, null-forgiving operators, and nullable reference types - **Pattern Matching** - Type, relational, and logical patterns @@ -21,6 +21,7 @@ Whether you're building console applications, web services, desktop clients, or - **Modern C# Features** - Records, tuples, init accessors, and required members ### 2. **Naming Conventions** (`naming-conventions.md`) + - **Type Naming** - PascalCase for classes, interfaces, records, and structs - **Member Naming** - Methods, properties, fields, and constants - **Field Naming** - Private field prefixes, camelCase patterns @@ -30,6 +31,7 @@ Whether you're building console applications, web services, desktop clients, or - **Avoiding Ambiguity** - Clarity, consistency, and avoiding reserved words ### 3. **Async/Await Patterns** (`async-patterns.md`) + - **Async Fundamentals** - Tasks, synchronous contexts, and proper async patterns - **ConfigureAwait** - Using ConfigureAwait(false) in libraries and UI applications - **Cancellation Tokens** - Proper cancellation propagation and timeout handling @@ -41,6 +43,7 @@ Whether you're building console applications, web services, desktop clients, or ## When to Use This Plugin Use this plugin when you're: + - Starting a new C# project and need consistent patterns - Reviewing code for alignment with industry best practices - Training junior developers on C# idioms @@ -62,112 +65,30 @@ Or, if installing from a local directory: copilot plugin install ./plugins/csharp-best-practices ``` -After installation, the plugin's instruction files will be automatically applied when you work with C# code. - -## Quick Examples - -### Naming Convention -```csharp -// Good: PascalCase for public members -public class UserService -{ - private string _internalCache; // Underscore prefix for private fields - - public string UserName { get; set; } // PascalCase for properties - - public async Task GetUserAsync(int userId) // Async suffix - { - return await _repository.FindAsync(userId); - } -} - -// Avoid: camelCase for public or inconsistent naming -public class userService // Should be UserService -{ - public string userName { get; set; } // Should be UserName -} -``` - -### Null Handling -```csharp -// Good: Modern null coalescing -string? description = user?.Address?.Description ?? "No description"; - -// Good: Null-forgiving operator when you're sure -var street = user!.Address!.Street; // Use only when certain - -// Good: Null coalescing assignment -user.LastModified ??= DateTime.UtcNow; -``` - -### Async Patterns -```csharp -// Good: Library method with ConfigureAwait(false) -public async Task GetUserAsync(int id, CancellationToken cancellationToken = default) -{ - using var response = await _client.GetAsync($"/users/{id}", cancellationToken) - .ConfigureAwait(false); - var json = await response.Content.ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - return JsonSerializer.Deserialize(json)!; -} - -// Avoid: Blocking on async code -var user = GetUserAsync(1).Result; // Deadlock risk - -// Avoid: async void except for events -public async void OnUserChanged() // Only acceptable for event handlers -{ - await UpdateUIAsync(); -} -``` - -### LINQ Patterns -```csharp -// Good: Query syntax for complex queries -var activeUsers = from user in users - where user.IsActive && user.JoinDate < cutoffDate - orderby user.Name - select new { user.Id, user.Name }; - -// Good: Method syntax for simple operations -var count = users.Where(u => u.IsActive).Count(); - -// Good: Deferred execution when needed -IEnumerable GetActiveUsers() -{ - return users.Where(u => u.IsActive); // Deferred -} - -// Good: Materialization when needed -List GetActiveUsersList() -{ - return users.Where(u => u.IsActive).ToList(); // Materialized -} -``` +After installation, the plugin will be automatically applied when you work with C# code. ## Language & Technology Stack -- **Language:** C# 13+ (leveraging latest language features) -- **Platforms:** .NET 8+, .NET Framework (where applicable) +- **Language:** C# 14 (leveraging latest language features) +- **Platforms:** .NET 10, .NET Framework (where applicable) - **Focus Areas:** Enterprise patterns, async/await, modern language features - **Code Standards:** Microsoft naming conventions, SOLID principles ## Resources & References ### Official Microsoft Documentation + - [C# Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/) - [.NET API Reference](https://learn.microsoft.com/en-us/dotnet/api/) - [C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) - [Async/Await Best Practices](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming) - [LINQ Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/linq/) -### Industry Standards -- [C# Coding Standards](https://en.wikibooks.org/wiki/C_Sharp_Programming) +### Community Resources + +- [Essential C#](https://essentialcsharp.com/home) - [Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/) - [Effective C#](https://www.informit.com/store/effective-csharp-50-specific-ways-to-improve-your-9780135704653) by Bill Wagner - -### Community Resources - [FxCop Rules](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/) - [StyleCop Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) - [Roslyn Analyzers](https://github.com/dotnet/roslyn-analyzers) @@ -179,17 +100,3 @@ List GetActiveUsersList() - **Design Patterns** - Gang of Four and architectural patterns - **.NET Enterprise** - Enterprise architecture for .NET applications - **Security Best Practices** - Safe coding patterns and vulnerability prevention - -## Contributing - -This plugin is part of the IntelliPlugins ecosystem. For contributions, feature requests, or bug reports, visit the [IntelliPlugins repository](https://github.com/IntelliTect/IntelliPlugins). - -## License - -MIT License - See LICENSE file for details - ---- - -**Version:** 1.0.0 -**Publisher:** IntelliTect -**Last Updated:** 2024 diff --git a/plugins/csharp-best-practices/plugin.json b/plugins/csharp-best-practices/plugin.json index 695e315..9b44576 100644 --- a/plugins/csharp-best-practices/plugin.json +++ b/plugins/csharp-best-practices/plugin.json @@ -2,7 +2,7 @@ "name": "csharp-best-practices", "displayName": "C# Best Practices", "description": "C#-specific language patterns, naming conventions, error handling, dependency injection, and async best practices for enterprise development.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/csharp-best-practices", @@ -22,9 +22,6 @@ "enterprise", "dotnet" ], - "categories": [ - "Language", - "Enterprise" - ], + "categories": ["Language", "Enterprise"], "skills": "skills/" } diff --git a/plugins/csharp-best-practices/skills/review-async/SKILL.md b/plugins/csharp-best-practices/skills/review-async/SKILL.md index 713c66f..5041765 100644 --- a/plugins/csharp-best-practices/skills/review-async/SKILL.md +++ b/plugins/csharp-best-practices/skills/review-async/SKILL.md @@ -10,6 +10,7 @@ Audit async/await usage in the current file or selected code for correctness, sa ## When to Use Invoke this skill when you: + - Want to check a file for async anti-patterns before code review - Suspect a deadlock or performance issue caused by improper async usage - Are migrating synchronous code to async and want a validation pass @@ -18,6 +19,7 @@ Invoke this skill when you: ## What This Skill Checks ### 1. Blocking on async (deadlock risk) + ```csharp // ❌ Dangerous — can deadlock in non-console contexts var result = GetDataAsync().Result; @@ -29,6 +31,7 @@ var result = await GetDataAsync(); ``` ### 2. async void (fire-and-forget danger) + ```csharp // ❌ Exceptions are unobservable public async void LoadData() { ... } @@ -38,6 +41,7 @@ public async Task LoadDataAsync() { ... } ``` ### 3. Missing ConfigureAwait(false) in library code + ```csharp // ❌ In a library — may cause deadlocks in UI/ASP.NET contexts await _client.GetAsync(url); @@ -47,6 +51,7 @@ await _client.GetAsync(url).ConfigureAwait(false); ``` ### 4. Unnecessary async wrapper + ```csharp // ❌ Unnecessary state machine overhead public async Task GetCountAsync() => await _repo.CountAsync(); @@ -56,6 +61,7 @@ public Task GetCountAsync() => _repo.CountAsync(); ``` ### 5. Missing CancellationToken propagation + ```csharp // ❌ Ignores cancellation public async Task ProcessAsync() { await _db.SaveChangesAsync(); } @@ -66,6 +72,7 @@ public async Task ProcessAsync(CancellationToken ct = default) ``` ### 6. Unobserved task (fire-and-forget without handling) + ```csharp // ❌ Exception is swallowed _ = DoWorkAsync(); @@ -78,12 +85,12 @@ _ = DoWorkAsync().ContinueWith(t => _logger.LogError(t.Exception, "Background ta ## Review Output For each issue found, report: + - **Location**: file name and line number - **Issue type**: one of the categories above - **Severity**: Warning or Error - **Suggested fix**: corrected code snippet - ## Reference: Async Patterns # C# Async/Await Best Practices @@ -91,6 +98,7 @@ For each issue found, report: Comprehensive guide to asynchronous programming in C# using async/await, Tasks, and cancellation patterns. ## Table of Contents + 1. [Async Fundamentals](#async-fundamentals) 2. [ConfigureAwait](#configureawait) 3. [Cancellation Tokens](#cancellation-tokens) @@ -188,7 +196,7 @@ public class DataService { return _repository.GetUser(userId); } - + public async Task GetUserAsync(int userId) // Asynchronous (non-blocking) { return await _repository.GetUserAsync(userId); @@ -213,7 +221,7 @@ public class ModernDataService ### Library Code vs Application Code -Use **ConfigureAwait(false)** in library code to avoid UI context capture. +Use **ConfigureAwait(false)** when applicable in general-purpose library code to avoid UI context capture. ```csharp // Good: Library code with ConfigureAwait(false) @@ -223,10 +231,10 @@ public class UserRepository : IUserRepository { using var response = await _httpClient.GetAsync($"/users/{userId}") .ConfigureAwait(false); - + var json = await response.Content.ReadAsStringAsync() .ConfigureAwait(false); - + return JsonSerializer.Deserialize(json); } } @@ -275,17 +283,17 @@ public async Task> GetActiveUsersAsync() { var users = await _repository.GetAllUsersAsync() .ConfigureAwait(false); - + var activeUsers = users .Where(u => u.IsActive) .ToList(); - + foreach (var user in activeUsers) { user.LastAccessed = await GetLastAccessTimeAsync(user.Id) .ConfigureAwait(false); } - + return activeUsers; } @@ -294,12 +302,12 @@ public async Task> GetActiveUsersAsync() { var users = await _repository.GetAllUsersAsync().ConfigureAwait(false); var activeUsers = users.Where(u => u.IsActive).ToList(); - + foreach (var user in activeUsers) { user.LastAccessed = await GetLastAccessTimeAsync(user.Id); // Missing ConfigureAwait } - + return activeUsers; } ``` @@ -318,10 +326,10 @@ public async Task GetUserAsync(int userId, CancellationToken cancellationT { var response = await _httpClient.GetAsync($"/users/{userId}", cancellationToken) .ConfigureAwait(false); - + var json = await response.Content.ReadAsStringAsync(cancellationToken) .ConfigureAwait(false); - + return JsonSerializer.Deserialize(json)!; } @@ -329,17 +337,17 @@ public async Task GetUserAsync(int userId, CancellationToken cancellationT public async Task> GetAllUsersAsync(CancellationToken cancellationToken = default) { var users = new List(); - + for (int i = 0; i < totalPages; i++) { cancellationToken.ThrowIfCancellationRequested(); - + var pageUsers = await GetPageAsync(i, cancellationToken) .ConfigureAwait(false); - + users.AddRange(pageUsers); } - + return users; } @@ -370,12 +378,12 @@ public async Task ProcessItemsAsync(List items, CancellationToken cancella public async Task FetchWithTimeoutAsync(string url, int timeoutMs) { using var cts = new CancellationTokenSource(timeoutMs); - + try { var response = await _httpClient.GetAsync(url, cts.Token) .ConfigureAwait(false); - + return await response.Content.ReadAsStringAsync(cts.Token) .ConfigureAwait(false); } @@ -413,7 +421,7 @@ public async Task ExecuteWithCombinedCancellationAsync( { using var cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken); cts.CancelAfter(TimeSpan.FromSeconds(60)); - + return await DoWorkAsync(cts.Token); } @@ -441,10 +449,10 @@ public async Task GetUserAsync(int userId) { var user = await _repository.GetUserAsync(userId) .ConfigureAwait(false); - + if (user == null) throw new UserNotFoundException(userId); - + return user; } catch (HttpRequestException ex) @@ -496,7 +504,7 @@ When using Task.WhenAll, exceptions are wrapped in AggregateException. public async Task> GetUsersAsync(List userIds) { var tasks = userIds.Select(id => _repository.GetUserAsync(id)).ToList(); - + try { await Task.WhenAll(tasks).ConfigureAwait(false); @@ -513,7 +521,7 @@ public async Task> GetUsersAsync(List userIds) public async Task> GetUsersAsync(List userIds) { var tasks = userIds.Select(id => _repository.GetUserAsync(id)).ToList(); - + try { await Task.WhenAll(tasks).ConfigureAwait(false); @@ -526,7 +534,7 @@ public async Task> GetUsersAsync(List userIds) } throw; } - + return tasks.Select(t => t.Result).ToList(); } @@ -535,7 +543,7 @@ public async Task> GetUsersWithFallbackAsync(List userIds) { var results = new List(); var tasks = userIds.Select(id => _repository.GetUserAsync(id)).ToList(); - + await foreach (var task in Task.WhenEach(tasks)) { try @@ -548,7 +556,7 @@ public async Task> GetUsersWithFallbackAsync(List userIds) _logger.LogWarning(ex, "Failed to fetch single user"); } } - + return results; } ``` @@ -656,7 +664,7 @@ public async Task CreateOrderAsync(Order order) public async Task CreateOrderAsync(Order order) { await _repository.SaveAsync(order); - + // Fire-and-forget with error handling _ = _notificationService.NotifyAsync(order.CustomerId) .ContinueWith(t => @@ -680,7 +688,7 @@ public async Task ProcessUsersAsync(List users) var profile = await _service.GetProfileAsync(u.Id); users.Add(profile); // Race condition! }); - + await Task.WhenAll(tasks); } @@ -696,13 +704,13 @@ public async Task> GetProfilesAsync(List users) public async Task ProcessUsersAsync(List users) { var profiles = new ConcurrentBag(); - + var tasks = users.Select(async u => { var profile = await _service.GetProfileAsync(u.Id); profiles.Add(profile); // Thread-safe }); - + await Task.WhenAll(tasks).ConfigureAwait(false); } ``` @@ -722,10 +730,10 @@ public async Task GetOrderDetailsAsync(int orderId) var orderTask = _orderService.GetOrderAsync(orderId); var itemsTask = _orderService.GetItemsAsync(orderId); var shippingTask = _shippingService.GetShippingInfoAsync(orderId); - + await Task.WhenAll(orderTask, itemsTask, shippingTask) .ConfigureAwait(false); - + return new OrderSummary { Order = orderTask.Result, @@ -747,7 +755,7 @@ public async Task> GetManyUsersAsync(List userIds) { // Limit parallel operations to avoid resource exhaustion var semaphore = new SemaphoreSlim(10); // Max 10 concurrent - + var tasks = userIds.Select(async id => { await semaphore.WaitAsync(); @@ -760,7 +768,7 @@ public async Task> GetManyUsersAsync(List userIds) semaphore.Release(); } }); - + return (await Task.WhenAll(tasks)).ToList(); } ``` @@ -779,7 +787,7 @@ public async Task GetDataFromFastestProviderAsync() _provider2.FetchAsync(), _provider3.FetchAsync() }; - + var completed = await Task.WhenAny(tasks).ConfigureAwait(false); return completed.Result; } @@ -789,13 +797,13 @@ public async Task FetchWithTimeoutAsync(string url, int timeoutMs) { var fetchTask = _httpClient.GetStringAsync(url); var timeoutTask = Task.Delay(timeoutMs); - + var completed = await Task.WhenAny(fetchTask, timeoutTask) .ConfigureAwait(false); - + if (completed == timeoutTask) return null; // Timeout occurred - + return await fetchTask; } @@ -808,18 +816,18 @@ public async Task FindUserInSourcesAsync(string email) _database.FindAsync(email), _api.FindAsync(email) }; - + while (sources.Length > 0) { var completed = await Task.WhenAny(sources).ConfigureAwait(false); var user = await (Task)completed; - + if (user != null) return user; - + sources = sources.Where(t => t != completed).ToArray(); } - + return null; } ``` @@ -836,10 +844,10 @@ public async Task CreateOrderAsync(CreateOrderRequest request) { var customer = await ValidateCustomerAsync(request.CustomerId); var items = await ValidateItemsAsync(request.Items); - var order = await _repository.CreateAsync(new Order - { - Customer = customer, - Items = items + var order = await _repository.CreateAsync(new Order + { + Customer = customer, + Items = items }); await _notificationService.NotifyAsync(customer.Email); return order; @@ -884,7 +892,7 @@ public async Task AnalyzeDataAsync(List datasets) { var analyzedTasks = datasets.Select(d => AnalyzeAsync(d)).ToArray(); var results = await Task.WhenAll(analyzedTasks).ConfigureAwait(false); - + var combined = CombineResults(results); return await FinalizeAsync(combined); } @@ -929,7 +937,7 @@ public async Task ValidateAsync(User user) { if (user.IsValid) return; // Returns Task.CompletedTask - + await _emailService.SendInvalidNoticeAsync(user); } @@ -938,7 +946,7 @@ public async ValueTask GetUserAsync(int userId) { if (_cache.TryGetValue(userId, out var user)) return user; // Returns ValueTask - no allocation - + return await _repository.GetUserAsync(userId); } @@ -947,7 +955,7 @@ public ValueTask ParseAsync(string value) { if (int.TryParse(value, out var result)) return new ValueTask(result); // No async overhead - + return new ValueTask(ParseFromServiceAsync(value)); } ``` @@ -959,10 +967,10 @@ public ValueTask ParseAsync(string value) public async Task GetUserAsync(int userId) { using var timer = _telemetry.StartTimer("GetUser"); - + var user = await _repository.GetUserAsync(userId) .ConfigureAwait(false); - + timer.Stop(); return user; } @@ -971,7 +979,7 @@ public async Task GetUserAsync(int userId) public async Task GetUserWithTimeoutAsync(int userId) { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - + try { return await _repository.GetUserAsync(userId, cts.Token) @@ -993,7 +1001,7 @@ Async/await best practices: 1. **Always use async/await** for I/O-bound operations 2. **Add Async suffix** to Task-returning methods -3. **Use ConfigureAwait(false)** in library code +3. **Use ConfigureAwait(false)** in general-purpose library code when applicable 4. **Accept and propagate** CancellationTokens 5. **Never block on async** code (no .Result or .Wait()) 6. **Avoid async void** except for event handlers diff --git a/plugins/enterprise-bug-fixing/README.md b/plugins/enterprise-bug-fixing/README.md index 98cd87b..97ce0c8 100644 --- a/plugins/enterprise-bug-fixing/README.md +++ b/plugins/enterprise-bug-fixing/README.md @@ -9,7 +9,6 @@ A professional-grade bug fixing plugin that enforces a systematic, test-first ap - **Automated Workflows**: Feature branch naming from work items with git automation - **Root Cause Analysis**: Structured approach to identifying the underlying problem - **Comprehensive Validation**: Multi-stage validation including builds, tests, and code quality checks -- **Model Generation Support**: Automatic Coalesce model regeneration when data models change - **Pre-Completion Checklists**: Ensures no critical steps are missed ## Installation @@ -35,21 +34,23 @@ Enterprise bug fixing is not just about making changes to code—it's about: Tests should validate **business requirements**, not just code coverage: ### Good Test + ```csharp public void Order_WhenCancelledAfterShipment_ShouldNotRefundCustomer() { // Arrange var order = CreateShippedOrder(); - + // Act var result = order.Cancel(); - + // Assert Assert.False(result.IsRefundApproved); } ``` ### Bad Test + ```csharp public void CancelOrder_SetsStatusToCancelled() { @@ -75,10 +76,12 @@ Branches follow a consistent naming convention: ``` **Examples:** + - `kb/pbi12345` - Kevin Barnes fixing work item 12345 - `jd/pbi67890` - Jane Doe fixing work item 67890 Benefits: + - Easy to identify the work item associated with a branch - Consistent naming across teams - Automatic correlation with Azure DevOps @@ -88,26 +91,25 @@ Benefits: Every bug fix goes through comprehensive validation: ### Stage 1: Build Validation + - `dotnet build` succeeds - No compiler errors or warnings - All project references resolve correctly ### Stage 2: Test Validation + - Unit tests pass - Business requirement tests pass - No regressions in related tests ### Stage 3: Code Quality + - Architecture principles adhered to (SOLID, DRY) - No code quality violations - Proper error handling and logging -### Stage 4: Model Regeneration (if applicable) -- Coalesce models regenerated -- DTOs updated -- TypeScript definitions generated - ### Stage 5: Pre-Completion Checklist + - [ ] Work item understood and requirements clear - [ ] Feature branch created with correct naming - [ ] Bug fix implemented following project conventions @@ -115,43 +117,49 @@ Every bug fix goes through comprehensive validation: - [ ] Solution builds successfully - [ ] All relevant tests pass - [ ] Code quality checks passed -- [ ] Coalesce regenerated (if model changes made) - [ ] Ready for code review ## Typical Bug Fix Scenarios ### Scenario 1: Logic Error + **Problem**: A calculation is returning incorrect results **Approach**: + 1. Write a test that reproduces the incorrect calculation with sample data 2. Identify the root cause in the logic 3. Fix the calculation 4. Validate the test passes and no other tests break ### Scenario 2: Missing Validation + **Problem**: Invalid data is being accepted **Approach**: + 1. Write a test that demonstrates the invalid data being accepted 2. Add validation logic 3. Ensure error handling is appropriate 4. Test error messages are user-friendly ### Scenario 3: Data Model Issue + **Problem**: Entity relationships are incorrect or incomplete **Approach**: -1. Analyze the Coalesce model structure + +1. Analyze the data model structure 2. Write tests that demonstrate the issue -3. Modify the EF Core model -4. Run `coalesce_generate` to update DTOs and TypeScript -5. Validate tests pass and UI reflects changes +3. Modify the data model +4. Validate tests pass and UI reflects changes ### Scenario 4: Performance Problem + **Problem**: A query is taking too long **Approach**: + 1. Write a performance test with realistic data 2. Analyze the query execution plan 3. Add indexes or optimize the query @@ -175,70 +183,32 @@ Follow architecture guidelines from **solid-principles** and **csharp-best-pract - Use proper dependency injection patterns - Implement correct async/await patterns -## Handling Model Changes (Coalesce) - -When fixing bugs related to data models: - -1. **Modify the EF Core Model**: Update C# entities as needed -2. **Update Coalesce Metadata**: Adjust Coalesce attributes if necessary -3. **Generate Code**: Run `coalesce_generate` to regenerate: - - Data Transfer Objects (DTOs) - - TypeScript service layer - - Api controllers and endpoints -4. **Validate Generated Code**: Review generated code for correctness -5. **Update UI**: Modify Vue components if necessary -6. **Test End-to-End**: Verify the fix works through the full stack - -## Code Generation Workflow - -### When to Regenerate Models -- After modifying EF Core entities -- After changing Coalesce attributes -- After adding new properties or relationships - -### Generation Process -```bash -coalesce_generate -``` - -This regenerates: -- `Generated/` directory with DTOs -- TypeScript services -- API controllers -- Query endpoints - -### Validation After Generation -1. Review new generated code -2. Ensure custom code in `*.Custom.cs` files is preserved -3. Run all tests -4. Verify UI bindings still work - ## Best Practices ### Do's -- Write tests before or immediately after understanding the bug -- Keep fixes minimal and focused on the specific issue -- Follow existing project conventions and patterns -- Document why the fix works, not just what changed -- Consider edge cases and potential regressions -- Review requirements thoroughly before coding -- Use feature branches for all work -- Run validation checks before requesting review + +- Write tests before or immediately after understanding the bug +- Keep fixes minimal and focused on the specific issue +- Follow existing project conventions and patterns +- Document why the fix works, not just what changed +- Consider edge cases and potential regressions +- Review requirements thoroughly before coding +- Use feature branches for all work +- Run validation checks before requesting review ### Don'ts -- Don't modify unrelated code -- Don't skip the test-first approach -- Don't commit directly to main -- Don't ignore compiler warnings -- Don't merge without passing all tests -- Don't assume the fix is correct without validation -- Don't create overly complex fixes + +- Don't modify unrelated code +- Don't skip the test-first approach +- Don't commit directly to main +- Don't ignore compiler warnings +- Don't merge without passing all tests +- Don't assume the fix is correct without validation +- Don't create overly complex fixes ## Resources - **Azure DevOps Documentation**: [Azure DevOps Docs](https://docs.microsoft.com/en-us/azure/devops/) -- **Entity Framework Core**: [EF Core Documentation](https://docs.microsoft.com/en-us/ef/core/) -- **Coalesce Framework**: [Coalesce Documentation](https://intellitect.com/coalesce/) - **Testing Best Practices**: See testing-essentials plugin - **Architecture Guidelines**: See solid-principles plugin @@ -253,20 +223,3 @@ Before starting work: - [ ] Checked for related work items or dependencies - [ ] Confirmed feature branch naming convention - [ ] Have necessary tools installed (dotnet, npm, git) - -## Support & Contributing - -For issues or suggestions: - -1. Check existing documentation -2. Review similar bug fixes -3. Consult architecture and testing guidelines -4. Reach out to the IntelliTect team - -## License - -MIT License - See LICENSE file for details - ---- - -**IntelliTect** - Enterprise Software Solutions diff --git a/plugins/enterprise-bug-fixing/agents/BugFixerAgent.agent.md b/plugins/enterprise-bug-fixing/agents/BugFixerAgent.agent.md index 6bebea5..f4c5d70 100644 --- a/plugins/enterprise-bug-fixing/agents/BugFixerAgent.agent.md +++ b/plugins/enterprise-bug-fixing/agents/BugFixerAgent.agent.md @@ -1,6 +1,38 @@ --- description: Fix bugs with a systematic, test-first approach integrated with Azure DevOps -tools: ['edit/createFile', 'edit/createDirectory', 'edit/editFiles', 'search', 'new', 'runCommands', 'runTasks', 'ado_with_filtered_domains/*', 'Coalesce/*', 'context7/*', 'microsoftdocs/*', 'playwright/*', 'nuget/*', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] +tools: + [ + "edit/createFile", + "edit/createDirectory", + "edit/editFiles", + "search", + "vscode/getProjectSetupInfo", + "vscode/installExtension", + "vscode/newWorkspace", + "vscode/runCommand", + "execute/getTerminalOutput", + "execute/runInTerminal", + "read/terminalLastCommand", + "read/terminalSelection", + "execute/createAndRunTask", + "ado_with_filtered_domains/*", + "Coalesce/*", + "context7/*", + "microsoftdocs/*", + "playwright/*", + "nuget/*", + "search/usages", + "vscode/vscodeAPI", + "read/problems", + "search/changes", + "testFailure", + "openSimpleBrowser", + "web/fetch", + "web/githubRepo", + "vscode/extensions", + "todo", + "runTests", + ] --- # Enterprise Bug Fixer Agent @@ -39,6 +71,7 @@ If the user has not provided a work item number, immediately ask: > Please provide the Azure DevOps work item number for the bug or issue you want me to fix (e.g., "12345" or "PBI 12345"). > > Optionally, also provide: +> > - Current branch (if already on a feature branch) > - Known affected components > - Any urgent deadlines or constraints @@ -109,19 +142,24 @@ Step 9: Ready for Code Review ### Step 3: Create Feature Branch 1. **Check Current Branch**: + ```bash git branch --show-current ``` + - If already on a feature branch (not `main`), confirm with user whether to use it or create new 2. **Determine User Initials**: + ```bash git config user.name ``` + - Extract first letter of first name + first letter of last name (lowercase) - If unclear, ask the user for preferred initials 3. **Create Feature Branch** following naming convention: + ``` {user_initials}/pbi{work_item_number} ``` @@ -152,30 +190,31 @@ Step 9: Ready for Code Review - Use descriptive names explaining the business scenario 3. **Test Structure** (Arrange-Act-Assert): + ```csharp [Fact] public void Order_WhenCancelledAfterShipment_ShouldNotRefundCustomer() { // Arrange - Set up test data var order = CreateShippedOrder(total: 100m); - + // Act - Perform the operation var result = order.Cancel(); - + // Assert - Verify business requirement Assert.False(result.IsRefundApproved, "Shipped orders should not be refunded"); } ``` 4. **Test Guidelines**: - - Tests should verify business requirements - - Use realistic test data - - Test names should describe the business scenario - - Each test should verify one business requirement - - Include both success and failure scenarios - - Don't test implementation details - - Don't aim for code coverage metrics alone - - Don't write tests that are too complex + - Tests should verify business requirements + - Use realistic test data + - Test names should describe the business scenario + - Each test should verify one business requirement + - Include both success and failure scenarios + - Don't test implementation details + - Don't aim for code coverage metrics alone + - Don't write tests that are too complex 5. **Run Tests** (they should fail): ```bash @@ -212,16 +251,20 @@ Step 9: Ready for Code Review ### Step 6: Validate the Fix 1. **Run Tests** (they should now pass): + ```bash dotnet test ``` + - Verify that the tests you wrote now pass - Check that no other tests were broken 2. **Build the Solution**: + ```bash dotnet build ``` + - No compiler errors - No compiler warnings (or document why they're acceptable) @@ -247,9 +290,11 @@ Only complete this step if the bug fix involved changing EF Core models or Coale - Were Coalesce attributes modified? 2. **Regenerate Models**: + ```bash coalesce_generate ``` + - This regenerates DTOs in the `Generated/` directory - Updates TypeScript service layer - Updates API controllers and endpoints @@ -270,6 +315,7 @@ Only complete this step if the bug fix involved changing EF Core models or Coale dotnet test npm run lint ``` + - Ensure all tests still pass - Check for TypeScript or Vue linting errors @@ -295,26 +341,31 @@ Before considering the fix complete, verify: The agent uses the following tools: ### Git & Version Control + - `git branch --show-current` - Check current branch - `git config user.name` - Get user name - `git fetch origin main` - Update main branch - `git checkout -b` - Create feature branch ### Build & Test + - `dotnet build` - Build the solution - `dotnet test` - Run unit tests - `coalesce_generate` - Regenerate Coalesce models ### Azure DevOps Integration + - `ado_with_filtered_domains/*` - Query work items from Azure DevOps - Extract work item details (title, description, acceptance criteria) ### Code Analysis + - `search` - Find relevant code - `problems` - Identify build/linting issues - `testFailure` - Analyze test failures ### Documentation + - `microsoft-docs-*` - Look up .NET/EF Core documentation - `context7` - Access Coalesce documentation @@ -334,6 +385,7 @@ The fix is complete when: ### Scenario: Bug in Business Logic **Workflow:** + 1. Identify the logic error 2. Write test that reproduces the incorrect behavior 3. Fix the logic @@ -342,6 +394,7 @@ The fix is complete when: ### Scenario: Missing Validation **Workflow:** + 1. Write test that shows invalid data being accepted 2. Add validation logic 3. Ensure error messages are clear and helpful @@ -350,6 +403,7 @@ The fix is complete when: ### Scenario: Data Model Issue **Workflow:** + 1. Analyze EF Core model structure 2. Write tests that demonstrate the issue 3. Modify entities/relationships @@ -359,6 +413,7 @@ The fix is complete when: ### Scenario: Performance Problem **Workflow:** + 1. Write performance test with realistic data 2. Analyze query execution 3. Optimize query or add indexes @@ -368,6 +423,7 @@ The fix is complete when: ### Scenario: Interaction Between Multiple Components **Workflow:** + 1. Understand how components interact 2. Write integration tests 3. Fix the interaction issue diff --git a/plugins/enterprise-bug-fixing/agents/README.md b/plugins/enterprise-bug-fixing/agents/README.md deleted file mode 100644 index a4d5d4f..0000000 --- a/plugins/enterprise-bug-fixing/agents/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# agents/ Directory - -This directory contains Copilot agent definitions for specialized workflows. - -Agents extend plugins with multi-step agentic workflows for complex tasks: - -- **BugFixerAgent** - Structured bug resolution workflow -- Future agents for specific scenarios - -## Agent Structure - -Each agent includes: -- Agent manifest (.agent.md) -- Step-by-step workflow documentation -- Tool requirements and permissions -- Acceptance criteria and validation steps diff --git a/plugins/enterprise-bug-fixing/plugin.json b/plugins/enterprise-bug-fixing/plugin.json index 42fd04f..09db864 100644 --- a/plugins/enterprise-bug-fixing/plugin.json +++ b/plugins/enterprise-bug-fixing/plugin.json @@ -2,7 +2,7 @@ "name": "enterprise-bug-fixing", "displayName": "Enterprise Bug Fixing Workflow", "description": "Structured bug resolution workflow with test-first approach, Azure DevOps integration, and comprehensive validation.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/enterprise-bug-fixing", @@ -21,10 +21,7 @@ "root-cause-analysis", "validation" ], - "categories": [ - "Specialized", - "Workflow" - ], + "categories": ["Specialized", "Workflow"], "agents": "agents/", "skills": "skills/", "mcpServers": ".mcp.json" diff --git a/plugins/solid-principles/README.md b/plugins/solid-principles/README.md index cbb7080..1dbfb01 100644 --- a/plugins/solid-principles/README.md +++ b/plugins/solid-principles/README.md @@ -90,6 +90,7 @@ With this plugin installed, Copilot will: **You ask:** "Create a UserRegistration service" **Copilot responds:** + ``` I'll create a UserService that follows SOLID principles: - IUserRepository for data access (Dependency Inversion) @@ -106,6 +107,7 @@ Does this approach work for you? **You ask:** "Review this code for architectural issues" **Copilot identifies:** + - God class with multiple responsibilities (SRP violation) - Direct static dependencies (DIP violation) - Missing validation (error handling) @@ -113,6 +115,7 @@ Does this approach work for you? - Magic strings (naming clarity) **Copilot recommends:** + - Break into focused services - Use dependency injection - Add validation helper @@ -121,14 +124,14 @@ Does this approach work for you? ## Key Features -### Do This +### Do This ```csharp // Single Responsibility - UserService handles only user logic public class UserService { private readonly IUserRepository _repository; - + public async Task RegisterAsync(User user) { // User registration logic only @@ -139,7 +142,7 @@ public class UserService public class OrderService { private readonly IPaymentProcessor _paymentProcessor; - + public OrderService(IPaymentProcessor paymentProcessor) { _paymentProcessor = paymentProcessor; @@ -158,7 +161,7 @@ public class UserServiceTests } ``` -### Avoid This +### Avoid This ```csharp // God class - violates SRP @@ -204,6 +207,7 @@ public class UserService ``` **Flow:** + 1. Controller receives HTTP request 2. Controller calls Service with request data 3. Service contains business logic, calls Repository @@ -214,23 +218,18 @@ public class UserService ### Dependency Injection ```csharp -// Startup - wire dependencies -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - } -} +// Program.cs - wire dependencies +// or do so in extension methods grouped logically +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Usage - dependencies injected automatically [ApiController] public class UsersController { private readonly IUserService _userService; - + public UsersController(IUserService userService) { _userService = userService; // Injected by container @@ -240,14 +239,6 @@ public class UsersController ## Code Quality Standards -### Naming Conventions - -- **Classes**: PascalCase, descriptive (`UserAuthenticationService`) -- **Methods**: PascalCase, action-oriented (`AuthenticateUserAsync`) -- **Variables**: camelCase, intention-revealing (`hashedPassword`) -- **Constants**: PascalCase, UPPER_SNAKE_CASE (`MaxPasswordAttempts`) -- **Interfaces**: PascalCase, prefix with `I` (`IUserRepository`) - ### Error Handling ```csharp @@ -296,12 +287,12 @@ public interface IUserRepository public class SqlUserRepository : IUserRepository { private readonly DbContext _dbContext; - + public SqlUserRepository(DbContext dbContext) { _dbContext = dbContext; } - + public async Task GetByIdAsync(int id) { var user = await _dbContext.Users.FindAsync(id); @@ -335,10 +326,10 @@ public class UserService : IUserService public async Task RegisterAsync(string email, string password) { ValidateInput(email, password); - + var hashedPassword = _passwordHasher.Hash(password); var user = new User { Email = email, PasswordHash = hashedPassword }; - + var created = await _repository.CreateAsync(user); return MapToDto(created); } @@ -370,18 +361,20 @@ public static class UserValidator ## Anti-Patterns to Avoid -### Service Locator +### Service Locator + ```csharp // Don't do this - hard to test, hidden dependencies var repository = ServiceLocator.GetService(); ``` -### Use Dependency Injection Instead +### Use Dependency Injection Instead + ```csharp public class UserService { private readonly IUserRepository _repository; - + public UserService(IUserRepository repository) { _repository = repository; @@ -389,18 +382,20 @@ public class UserService } ``` -### Static Dependencies +### Static Dependencies + ```csharp // Don't do this - couples to specific implementation user.PasswordHash = PasswordUtility.Hash(password); ``` -### Inject Dependencies +### Inject Dependencies + ```csharp public class UserService { private readonly IPasswordHasher _hasher; - + public UserService(IPasswordHasher hasher) { _hasher = hasher; @@ -408,7 +403,8 @@ public class UserService } ``` -### God Classes +### God Classes + ```csharp // Don't do this - too many responsibilities public class UserManager @@ -420,7 +416,8 @@ public class UserManager } ``` -### Focused Services +### Focused Services + ```csharp public class UserService { /* User operations */ } public class EmailService { /* Email operations */ } @@ -445,19 +442,24 @@ public class ReportService { /* Reporting */ } ## Troubleshooting ### Q: When should I use interfaces? + **A:** Use interfaces for all external dependencies and major abstractions. This enables dependency injection, testing, and flexibility to change implementations. ### Q: Is the repository pattern always necessary? + **A:** For simple CRUD operations with Entity Framework, repositories can add unnecessary abstraction. However, they're valuable when: + - You need to swap data sources (SQL to NoSQL) - You have complex queries to encapsulate - You want to mock data access in tests - You have multiple repositories with shared patterns ### Q: How much error handling is enough? + **A:** Handle only errors you can meaningfully recover from. For others, let exceptions bubble up (with logging). Use meaningful exception types and messages. ### Q: Can I mix SOLID with code-first development? + **A:** Absolutely. SOLID principles are about structure and relationships, not methodology. Start simple, refactor to SOLID as complexity grows. ## Related Plugins @@ -470,11 +472,13 @@ public class ReportService { /* Reporting */ } ## Resources ### Books -- *Clean Code* by Robert C. Martin -- *Clean Architecture* by Robert C. Martin -- *Dependency Injection in .NET* by Mark Seemann + +- _Clean Code_ by Robert C. Martin +- _Clean Architecture_ by Robert C. Martin +- _Dependency Injection in .NET_ by Mark Seemann ### Online + - [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) - [Design Patterns in C#](https://www.dofactory.com/net/design-patterns) - [Microsoft Dependency Injection](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) @@ -486,10 +490,6 @@ MIT License. See LICENSE file in the repository. ## Support For issues, questions, or contributions: + - GitHub: [IntelliPlugins](https://github.com/IntelliTect/IntelliPlugins) - Issues: [GitHub Issues](https://github.com/IntelliTect/IntelliPlugins/issues) - ---- - -**Last Updated:** 2024 -**Plugin Version:** 1.0.0 diff --git a/plugins/solid-principles/plugin.json b/plugins/solid-principles/plugin.json index bfdfff2..7d5b839 100644 --- a/plugins/solid-principles/plugin.json +++ b/plugins/solid-principles/plugin.json @@ -2,7 +2,7 @@ "name": "solid-principles", "displayName": "SOLID Principles & Architecture", "description": "Enterprise-grade guidance on SOLID principles, architecture patterns, and code quality for maintainable C# projects.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/solid-principles", @@ -22,10 +22,6 @@ "C#", "dotnet" ], - "categories": [ - "Enterprise", - "Architecture", - "Code Quality" - ], + "categories": ["Enterprise", "Architecture", "Code Quality"], "skills": "skills/" } diff --git a/plugins/solid-principles/skills/apply-solid/SKILL.md b/plugins/solid-principles/skills/apply-solid/SKILL.md index 807460c..c0f1460 100644 --- a/plugins/solid-principles/skills/apply-solid/SKILL.md +++ b/plugins/solid-principles/skills/apply-solid/SKILL.md @@ -1,6 +1,6 @@ --- name: apply-solid -description: Apply SOLID principles to C# code including SRP, OCP, LSP, ISP, and DIP +description: Enterprise-grade guidance on SOLID principles, architecture patterns, and code quality for maintainable C# projects. --- # Apply SOLID Principles Skill @@ -10,16 +10,12 @@ Apply SOLID architecture principles to C# codebases to improve maintainability, ## When to Use Invoke this skill when you: + - Need to review code for SOLID principle violations - Are refactoring a class or module to improve separation of concerns - Want to apply dependency inversion to decouple components - Are designing new classes and want to follow SOLID from the start ---- -description: "Enterprise-grade guidance on SOLID principles, architecture patterns, and code quality for maintainable C# projects." -applyTo: "**/*.cs" ---- - # SOLID Principles & Architecture Guidance ## Overview @@ -36,7 +32,7 @@ SOLID principles form the foundation of enterprise-grade, maintainable software. A class should have a single, well-defined responsibility. This ensures that the class is focused, easier to understand, and simpler to maintain. -#### Good Example +#### Good Example ```csharp // UserService handles only user business logic @@ -58,7 +54,7 @@ public class UserService var hashedPassword = _passwordHasher.Hash(password); var user = new User { Email = email, PasswordHash = hashedPassword }; - + return await _userRepository.CreateAsync(user); } } @@ -87,7 +83,7 @@ public class EmailService } ``` -#### Bad Example +#### Bad Example ```csharp // God class violates SRP - handles users, emails, AND data persistence @@ -109,6 +105,7 @@ public class UserManager ``` #### Why It Matters + - **Maintainability**: Changes to one responsibility don't affect others - **Testability**: Easier to test isolated units - **Reusability**: Classes can be used in different contexts @@ -122,7 +119,7 @@ public class UserManager Design classes so that new functionality can be added without changing existing code. Use abstraction and polymorphism. -#### Good Example +#### Good Example ```csharp // Abstraction allows extensions without modification @@ -186,7 +183,7 @@ var tieredDiscount = new TieredDiscount(...); var pricingEngine = new PricingEngine(tieredDiscount); ``` -#### Bad Example +#### Bad Example ```csharp // Violates OCP - must modify for every new discount type @@ -213,6 +210,7 @@ public class PricingEngine ``` #### Why It Matters + - **Minimal Risk**: New features don't affect existing code - **Scalability**: Easy to add new behaviors - **Maintainability**: Existing code stays stable @@ -226,7 +224,7 @@ public class PricingEngine If a class derives from a base class or implements an interface, it must fulfill the contract completely and not violate the expectations of the caller. -#### Good Example +#### Good Example ```csharp public abstract class Bird @@ -269,7 +267,7 @@ public void FeedBird(Bird bird) } ``` -#### Bad Example +#### Bad Example ```csharp // Violates LSP - Penguin can't fly but inherits FlyingBird @@ -281,7 +279,7 @@ public class FlyingBird : Bird public class Penguin : FlyingBird { public override void Eat() => Console.WriteLine("Penguin eats fish"); - + // Violates the contract - penguin can't fly! public override void Fly() => throw new NotImplementedException("Penguins cannot fly"); } @@ -294,6 +292,7 @@ public void MakeBirdFly(FlyingBird bird) ``` #### Why It Matters + - **Predictability**: Polymorphism works as expected - **Robustness**: No runtime surprises with subtype behavior - **Contract Integrity**: Interfaces and base classes represent reliable contracts @@ -307,7 +306,7 @@ public void MakeBirdFly(FlyingBird bird) Create fine-grained, focused interfaces rather than bloated "fat" interfaces. Clients should only depend on the methods they actually need. -#### Good Example +#### Good Example ```csharp // Segregated interfaces - clients depend only on what they use @@ -343,7 +342,7 @@ public class ConsoleStream : IReader, IWriter public class DataProcessor { private readonly IReader _reader; - + public DataProcessor(IReader reader) { _reader = reader; @@ -357,7 +356,7 @@ public class DataProcessor } ``` -#### Bad Example +#### Bad Example ```csharp // Fat interface forces unnecessary dependencies @@ -374,7 +373,7 @@ public class ConsoleStream : IStream { public string Read() => Console.ReadLine(); public void Write(string content) => Console.WriteLine(content); - + // Must implement methods it doesn't use public void Seek(int position) => throw new NotImplementedException(); public void Encrypt(byte[] key) => throw new NotImplementedException(); @@ -389,6 +388,7 @@ public class DataProcessor ``` #### Why It Matters + - **Flexibility**: Classes aren't tied to unnecessary methods - **Clarity**: Interfaces document specific contracts - **Loose Coupling**: Fewer dependencies between classes @@ -402,7 +402,7 @@ public class DataProcessor Depend on abstractions (interfaces), not concrete implementations. This inverts the typical dependency flow and increases flexibility. -#### Good Example +#### Good Example ```csharp // Abstractions at the top @@ -477,7 +477,7 @@ var emailService = new SmtpEmailService(smtpClient); var userService = new UserService(userRepository, emailService); ``` -#### Bad Example +#### Bad Example ```csharp // High-level module depends directly on low-level implementations @@ -498,6 +498,7 @@ public class UserService ``` #### Why It Matters + - **Flexibility**: Easy to swap implementations (databases, email providers, etc.) - **Testability**: Mock dependencies during testing - **Maintainability**: Changes to implementations don't affect high-level logic @@ -577,29 +578,6 @@ public class UserRepository : IUserRepository Use a DI container to manage object lifecycles and dependencies. -```csharp -// Startup configuration -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - // Register dependencies - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddControllers(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } -} -``` - --- ## Code Quality Guidelines @@ -608,7 +586,7 @@ public class Startup Extract duplicated code into reusable methods, classes, or services. -#### Good Example +#### Good Example ```csharp public class ValidationHelper @@ -641,7 +619,7 @@ public class UserService } ``` -#### Bad Example +#### Bad Example ```csharp public class UserService @@ -678,7 +656,7 @@ public class ProfileService Don't add features or complexity you don't need now. Keep code simple and focused on current requirements. -#### Good Example +#### Good Example ```csharp public class OrderService @@ -687,16 +665,16 @@ public class OrderService { var items = request.Items; var total = items.Sum(i => i.Price * i.Quantity); - + var order = new Order { Items = items, Total = total }; await _orderRepository.CreateAsync(order); - + return order; } } ``` -#### Bad Example +#### Bad Example ```csharp public class OrderService @@ -719,7 +697,7 @@ public class OrderService Use clear, intention-revealing names that describe purpose and behavior. -#### Good Example +#### Good Example ```csharp public interface IUserPasswordValidator @@ -746,7 +724,7 @@ public class UserAuthenticationService } ``` -#### Bad Example +#### Bad Example ```csharp public interface IValidator { bool Validate(string input); } @@ -774,7 +752,7 @@ public class AuthService Throw specific exceptions with descriptive messages. -#### Good Example +#### Good Example ```csharp public class UserService @@ -785,7 +763,7 @@ public class UserService throw new ArgumentException("User ID must be greater than zero.", nameof(userId)); var user = await _userRepository.GetByIdAsync(userId); - + if (user == null) throw new EntityNotFoundException($"User with ID {userId} not found."); @@ -800,7 +778,7 @@ public class EntityNotFoundException : Exception } ``` -#### Bad Example +#### Bad Example ```csharp public class UserService @@ -824,7 +802,7 @@ public class UserService Never catch an exception and silently ignore it without logging or rethrowing. -#### Good Example +#### Good Example ```csharp public async Task ProcessOrderAsync(Order order) @@ -841,7 +819,7 @@ public async Task ProcessOrderAsync(Order order) } ``` -#### Bad Example +#### Bad Example ```csharp public async Task ProcessOrderAsync(Order order) @@ -859,85 +837,13 @@ public async Task ProcessOrderAsync(Order order) --- -## Testing Requirements - -### Unit Test Strategy - -- **Service Layer Tests**: Test all business logic in services -- **Mocked Dependencies**: Use interfaces and DI to mock external dependencies -- **Arrange-Act-Assert**: Structure tests clearly -- **Edge Cases**: Test happy paths, errors, boundaries, null/empty inputs - -#### Good Example - -```csharp -[TestClass] -public class UserServiceTests -{ - private Mock _mockRepository; - private Mock _mockHasher; - private UserService _userService; - - [TestInitialize] - public void Setup() - { - _mockRepository = new Mock(); - _mockHasher = new Mock(); - _userService = new UserService(_mockRepository.Object, _mockHasher.Object); - } - - [TestMethod] - public async Task RegisterAsync_WithValidInput_CreatesUserSuccessfully() - { - // Arrange - const string email = "test@example.com"; - const string password = "ValidPassword123"; - const string hashedPassword = "hashed_value"; - - _mockHasher.Setup(h => h.Hash(password)).Returns(hashedPassword); - _mockRepository.Setup(r => r.CreateAsync(It.IsAny())) - .ReturnsAsync(new User { Id = 1, Email = email }); - - // Act - var result = await _userService.RegisterAsync(email, password); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(email, result.Email); - _mockRepository.Verify(r => r.CreateAsync(It.IsAny()), Times.Once); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public async Task RegisterAsync_WithInvalidEmail_ThrowsException() - { - // Arrange & Act - await _userService.RegisterAsync("invalid-email", "ValidPassword123"); - - // Assert: Exception expected - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public async Task RegisterAsync_WithShortPassword_ThrowsException() - { - // Arrange & Act - await _userService.RegisterAsync("test@example.com", "short"); - - // Assert: Exception expected - } -} -``` - ---- - ## Anti-Patterns to Avoid ### Service Locator Pattern Avoid using a service locator; use dependency injection instead. -#### Bad +#### Bad ```csharp public class UserService @@ -950,7 +856,7 @@ public class UserService } ``` -#### Good +#### Good ```csharp public class UserService @@ -973,7 +879,7 @@ public class UserService Avoid static utility classes for business logic; use dependency injection. -#### Bad +#### Bad ```csharp public class UserService @@ -987,7 +893,7 @@ public class UserService } ``` -#### Good +#### Good ```csharp public class UserService @@ -1007,7 +913,7 @@ public class UserService Avoid hardcoded values; use named constants or configuration. -#### Bad +#### Bad ```csharp public class OrderService @@ -1027,7 +933,7 @@ public class OrderService } ``` -#### Good +#### Good ```csharp public class OrderService @@ -1054,7 +960,7 @@ public class OrderService Avoid large classes with too many responsibilities. -#### Bad +#### Bad ```csharp public class UserManager @@ -1070,7 +976,7 @@ public class UserManager } ``` -#### Good +#### Good ```csharp public class UserService { /* User operations */ } @@ -1101,6 +1007,7 @@ Always explain the benefits: testability, maintainability, flexibility, and redu Before implementing, explain your intended architecture: "I'm planning to: + - Create a `{Name}Service` class for business logic - Implement an `I{Name}Repository` interface for data access - Use constructor injection for all dependencies @@ -1114,6 +1021,7 @@ Does this approach work for you, or would you prefer a different architecture?" ## Summary **SOLID principles are non-negotiable for enterprise code.** They ensure: + - Code is maintainable and easier to understand - Changes are localized and don't break unrelated parts - Testing is straightforward with proper abstractions diff --git a/plugins/testing-essentials/README.md b/plugins/testing-essentials/README.md index 15c1da6..ced7461 100644 --- a/plugins/testing-essentials/README.md +++ b/plugins/testing-essentials/README.md @@ -30,10 +30,10 @@ public void OrderCancellation_WithinThirtyDays_RefundsFull() // Arrange var order = CreateOrderFromThirtyDaysAgo(); var refundService = new RefundService(); - + // Act var refund = refundService.CalculateRefund(order); - + // Assert Assert.Equal(order.Total, refund.Amount); } @@ -102,10 +102,10 @@ public void SendNotification_OnOrderConfirmed_CallsEmailService() var mockEmailService = new Mock(); var notificationHandler = new OrderNotificationHandler(mockEmailService.Object); var order = new Order { CustomerId = 123 }; - + // Act notificationHandler.OnOrderConfirmed(order); - + // Assert - verify the mock was called correctly mockEmailService.Verify( x => x.Send(It.Is(e => e.CustomerId == 123)), @@ -177,7 +177,7 @@ public void CalculateBonus_WithVariousSalaries_ReturnsCorrectAmount(int salary) ## Testing Frameworks -The enterprise standard is **xUnit**, paired with **Moq** for mocking: +The enterprise standard is **xUnit.v3**, paired with **Moq** for mocking: ```csharp using Xunit; @@ -195,7 +195,9 @@ public class OrderServiceTests ``` **Other common frameworks:** -- **NUnit**: Similar to xUnit, still widely used + +- **NUnit**: Similar to xUnit.v3, still widely used +- **TUnit**: Similar to xUnit.v3, very fast - **NSubstitute**: Alternative to Moq, elegant syntax - **FluentAssertions**: Chainable assertions for readability @@ -263,6 +265,7 @@ copilot plugin install testing-essentials@IntelliPlugins ``` Once installed, reference these guides when: + - Setting up a new test project - Writing unit tests for business logic - Designing test data and fixtures @@ -297,7 +300,7 @@ public class PaymentProcessorTests CardToken = "valid-token-123", CustomerId = 456 }; - + _mockGateway .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Returns(new ChargeResult { Success = true, TransactionId = "txn-789" }); @@ -308,11 +311,11 @@ public class PaymentProcessorTests // Assert Assert.True(result.IsSuccessful); Assert.Equal("txn-789", result.TransactionId); - + _mockGateway.Verify( x => x.Charge("valid-token-123", 99.99m), Times.Once); - + _mockLogger.Verify( x => x.Log(It.Is(m => m.Contains("Payment processed"))), Times.Once); @@ -323,7 +326,7 @@ public class PaymentProcessorTests { // Arrange var payment = new Payment { CardToken = "declined-token" }; - + _mockGateway .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Returns(new ChargeResult { Success = false, Error = "Card declined" }); @@ -341,14 +344,14 @@ public class PaymentProcessorTests { // Arrange var payment = new Payment { CardToken = "any-token" }; - + _mockGateway .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Throws(); // Act & Assert Assert.Throws(() => _processor.ProcessPayment(payment)); - + _mockLogger.Verify( x => x.LogError(It.IsAny()), Times.Once); @@ -365,9 +368,6 @@ public class PaymentProcessorTests ## Additional Resources For detailed testing patterns, strategies, and advanced mocking techniques, see: + - `instructions/testing-best-practices.md` — Testing philosophy and core patterns - `instructions/unit-testing-patterns.md` — Advanced patterns, data-driven tests, and infrastructure - -## License - -MIT - See LICENSE file in the repository. diff --git a/plugins/testing-essentials/plugin.json b/plugins/testing-essentials/plugin.json index e44982c..de5ad4c 100644 --- a/plugins/testing-essentials/plugin.json +++ b/plugins/testing-essentials/plugin.json @@ -2,7 +2,7 @@ "name": "testing-essentials", "displayName": "Testing Essentials", "description": "Comprehensive testing best practices including unit testing strategies, test naming conventions, and mock frameworks.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/testing-essentials", @@ -19,9 +19,6 @@ "xUnit", "mocking" ], - "categories": [ - "Enterprise", - "Testing" - ], + "categories": ["Enterprise", "Testing"], "skills": "skills/" } diff --git a/plugins/testing-essentials/skills/apply-testing-patterns/SKILL.md b/plugins/testing-essentials/skills/apply-testing-patterns/SKILL.md index 80d5998..793106f 100644 --- a/plugins/testing-essentials/skills/apply-testing-patterns/SKILL.md +++ b/plugins/testing-essentials/skills/apply-testing-patterns/SKILL.md @@ -10,6 +10,7 @@ Apply advanced unit testing patterns including test doubles (mocks, stubs, fakes ## When to Use Invoke this skill when you: + - Need to implement complex test scenarios with multiple test doubles - Want to apply data-driven or theory-based test patterns - Are organizing a test suite and need structural guidance @@ -45,10 +46,10 @@ public void Example_Pattern_Demonstrated() // Arrange - set up the test scenario var service = new MyService(); var input = new MyInput { Value = 100 }; - + // Act - execute the behavior var result = service.ProcessInput(input); - + // Assert - verify the expected outcome Assert.NotNull(result); Assert.Equal(200, result.Value); @@ -66,32 +67,32 @@ public class OrderServiceTests public void PlaceOrder_WithValidCustomerAndItems_SuccessfullyCreatesOrder() { // Arrange - var customer = new Customer - { - Id = 1, - Name = "John Doe", - Email = "john@example.com" + var customer = new Customer + { + Id = 1, + Name = "John Doe", + Email = "john@example.com" }; - + var items = new[] { new OrderItem { ProductId = 1, Quantity = 2, Price = 50m }, new OrderItem { ProductId = 2, Quantity = 1, Price = 75m } }; - + var mockRepository = new Mock(); var mockNotificationService = new Mock(); var orderService = new OrderService(mockRepository.Object, mockNotificationService.Object); - + // Act var order = orderService.PlaceOrder(customer, items); - + // Assert - verify successful creation Assert.NotNull(order); Assert.Equal(customer.Id, order.CustomerId); Assert.Equal(3, order.TotalItems); Assert.Equal(175m, order.Total); - + // Verify dependencies were called correctly mockRepository.Verify(x => x.Save(order), Times.Once); mockNotificationService.Verify( @@ -104,22 +105,22 @@ public class OrderServiceTests { // Arrange var orderId = 123; - var expectedOrder = new Order - { - Id = orderId, - CustomerId = 1, + var expectedOrder = new Order + { + Id = orderId, + CustomerId = 1, Total = 250m, Status = OrderStatus.Confirmed }; - + var mockRepository = new Mock(); mockRepository.Setup(x => x.GetById(orderId)).Returns(expectedOrder); - + var orderService = new OrderService(mockRepository.Object, new Mock().Object); - + // Act var result = orderService.GetOrder(orderId); - + // Assert Assert.NotNull(result); Assert.Equal(orderId, result.Id); @@ -143,10 +144,10 @@ public class PaymentProcessorTests mockBankService .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Throws(); - + var processor = new PaymentProcessor(mockBankService.Object); var payment = new Payment { Amount = 1000m, CardToken = "token123" }; - + // Act & Assert Assert.Throws(() => processor.ProcessPayment(payment)); } @@ -157,18 +158,18 @@ public class PaymentProcessorTests // Arrange var mockBankService = new Mock(); var callCount = 0; - + mockBankService .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Callback(() => callCount++) .Throws(); - + var processor = new PaymentProcessor(mockBankService.Object); var payment = new Payment { Amount = 100m, CardToken = "token123" }; - + // Act & Assert Assert.Throws(() => processor.ProcessPayment(payment)); - + // Verify retry logic (e.g., 3 attempts) mockBankService.Verify(x => x.Charge(It.IsAny(), It.IsAny()), Times.Exactly(3)); } @@ -181,13 +182,13 @@ public class PaymentProcessorTests mockBankService .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Returns(new ChargeResult { Success = false, Reason = "Invalid card" }); - + var processor = new PaymentProcessor(mockBankService.Object); var payment = new Payment { Amount = 100m, CardToken = "invalid" }; - + // Act var result = processor.ProcessPayment(payment); - + // Assert Assert.False(result.Success); Assert.Equal("Invalid card", result.Reason); @@ -199,16 +200,16 @@ public class PaymentProcessorTests // Arrange var mockBankService = new Mock(); var mockLogger = new Mock(); - + mockBankService .Setup(x => x.Charge(It.IsAny(), It.IsAny())) .Throws(); - + var processor = new PaymentProcessor(mockBankService.Object); processor.Logger = mockLogger.Object; - + var payment = new Payment { Amount = 100m, CardToken = "token123" }; - + // Act try { @@ -218,7 +219,7 @@ public class PaymentProcessorTests { // Expected } - + // Assert - verify error was logged for audit trail mockLogger.Verify( x => x.LogError(It.Is(msg => msg.Contains("Payment failed"))), @@ -244,15 +245,15 @@ public class DiscountCalculatorTests [InlineData(999999.99, 99999.99)] [InlineData(decimal.MaxValue - 1, decimal.MaxValue - 1)] // Large value public void CalculateDiscount_WithVariousPricePoints_ReturnsCorrectDiscount( - decimal price, + decimal price, decimal expectedDiscount) { // Arrange var calculator = new DiscountCalculator(); - + // Act var discount = calculator.Calculate(price); - + // Assert Assert.Equal(expectedDiscount, discount); } @@ -299,10 +300,10 @@ public class UserValidationTests { // Arrange var validator = new EmailValidator(); - + // Act var result = validator.IsValid(email); - + // Assert Assert.False(result); } @@ -314,7 +315,7 @@ public class UserValidationTests { // Arrange var calculator = new AverageCalculator(); - + // Act & Assert Assert.Throws(() => calculator.Calculate(numbers)); } @@ -324,7 +325,7 @@ public class UserValidationTests { // Arrange var service = new UserService(null); // Pass null dependency - + // Act & Assert Assert.Throws(() => service.FindUser(1)); } @@ -335,7 +336,7 @@ public class UserValidationTests // Arrange var mockRepository = new Mock(); var service = new OrderService(mockRepository.Object); - + // Act & Assert Assert.Throws(() => service.ProcessOrder(null)); } @@ -346,7 +347,7 @@ public class UserValidationTests // Arrange var service = new UserService(); var user = new User { Email = "user@example.com", Name = null }; - + // Act & Assert Assert.Throws(() => service.Create(user)); } @@ -358,7 +359,7 @@ public class UserValidationTests { // Arrange var finder = new MaximumFinder(); - + // Act & Assert - verify graceful handling if (numbers.Length == 0) { @@ -378,6 +379,7 @@ public class UserValidationTests ### When to Mock Mock when you need to: + - Isolate external dependencies (databases, APIs, file systems) - Verify interactions (what was called) - Control behavior (set up return values) @@ -391,10 +393,10 @@ public void SendEmail_OnOrderConfirmed_CallsEmailService() var mockEmailService = new Mock(); var orderService = new OrderService(mockEmailService.Object); var order = new Order { Id = 1, CustomerEmail = "user@example.com" }; - + // Act orderService.ConfirmOrder(order); - + // Assert mockEmailService.Verify( x => x.Send(It.Is(e => e.To == "user@example.com")), @@ -489,10 +491,10 @@ public void FindUser_WithFakeRepository_ReturnsUserWhenExists() var fakeRepo = new FakeUserRepository(); fakeRepo.Add(new User { Id = 1, Name = "John" }); var service = new UserService(fakeRepo); - + // Act var user = service.GetUser(1); - + // Assert Assert.NotNull(user); Assert.Equal("John", user.Name); @@ -512,12 +514,12 @@ public void GetUserAge_WithStub_ReturnsPresetValue() mockUserRepository .Setup(x => x.GetById(1)) .Returns(new User { Id = 1, Age = 30 }); // Stubbed response - + var service = new UserService(mockUserRepository.Object); - + // Act var age = service.GetUserAge(1); - + // Assert Assert.Equal(30, age); } @@ -535,10 +537,10 @@ public void PublishEvent_OnOrderCreated_CallsEventPublisher() var mockEventPublisher = new Mock(); var orderService = new OrderService(mockEventPublisher.Object); var order = new Order { Id = 1 }; - + // Act orderService.CreateOrder(order); - + // Assert - verify the mock was called correctly mockEventPublisher.Verify( x => x.Publish(It.Is(e => e.OrderId == 1)), @@ -577,9 +579,9 @@ public class UserServiceTests : ServiceTestBase { MockRepository.Setup(x => x.GetById(It.IsAny())).Throws(); var service = new UserService(MockRepository.Object, MockLogger.Object); - + Assert.Throws(() => service.GetUser(1)); - + VerifyErrorLogged("Failed to retrieve user"); } } @@ -631,10 +633,10 @@ public void GetActiveUsers_ReturnsOnlyActiveUsers() { var activeUser = new UserBuilder().Build(); var inactiveUser = new UserBuilder().WithId(2).Inactive().Build(); - + var service = new UserService(); var result = service.GetActiveUsers(new[] { activeUser, inactiveUser }); - + Assert.Single(result); Assert.Equal(activeUser.Id, result.First().Id); } @@ -676,7 +678,7 @@ public class CalculatorDataDrivenTests { var calculator = new DiscountCalculator(); var result = calculator.Calculate(price); - + Assert.Equal(expectedDiscount, result); } } @@ -720,6 +722,65 @@ public class EmailValidationTests } ``` +## Test Organization + +### Categorizing Tests with `[Trait]` + +Use `[Trait]` to group and filter tests by category, feature, or any custom label: + +```csharp +[Fact] +[Trait("Category", "Integration")] +public void GetUser_FromDatabase_ReturnsUser() { } + +[Fact] +[Trait("Category", "UnitTest")] +[Trait("Feature", "Billing")] +public void CalculateInvoice_WithTaxExempt_ExcludesTax() { } +``` + +Run only a specific category with `dotnet test --filter "Category=UnitTest"`. + +### Skipping Tests + +Skip a test conditionally with the `Skip` property on `[Fact]` or `[Theory]`: + +```csharp +[Fact(Skip = "Known flaky — tracked in GitHub #42")] +public void LegacyBehavior_StillWorks() { } + +[Theory(Skip = "External API not available in CI")] +[InlineData("us-east-1")] +[InlineData("eu-west-1")] +public void GetLatency_PerRegion_IsAcceptable(string region) { } +``` + +### Diagnostic Output with `ITestOutputHelper` + +Inject `ITestOutputHelper` to write diagnostic messages visible in test runners: + +```csharp +public class OrderServiceTests +{ + private readonly ITestOutputHelper _output; + + public OrderServiceTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void ProcessOrder_WithRetry_LogsAttempts() + { + _output.WriteLine("Starting retry scenario..."); + // ... test body ... + _output.WriteLine($"Result: {result}"); + } +} +``` + +Output only appears when a test fails (or when verbosity is set to detailed), keeping passing test runs clean. + ## Summary Effective test patterns: @@ -731,5 +792,6 @@ Effective test patterns: 5. **Mocking**: Isolate external dependencies 6. **Data-Driven**: Reduce duplication with multiple scenarios 7. **Parameterized**: Flexible test variations +8. **Organization**: Use `[Trait]` for filtering, `Skip` for known issues, `ITestOutputHelper` for diagnostics Use these patterns to build comprehensive, maintainable test suites that catch bugs early and document business requirements clearly. diff --git a/plugins/testing-essentials/skills/write-unit-tests/SKILL.md b/plugins/testing-essentials/skills/write-unit-tests/SKILL.md index bcb8fb1..151f3bd 100644 --- a/plugins/testing-essentials/skills/write-unit-tests/SKILL.md +++ b/plugins/testing-essentials/skills/write-unit-tests/SKILL.md @@ -7,6 +7,37 @@ description: Write xUnit unit tests for a C# class or method following Arrange-A Generate comprehensive xUnit unit tests for a specified C# class or method, covering the happy path, error conditions, boundary values, and edge cases. +## Project Setup + +### Test Project Conventions + +- Name test projects `[ProjectName].Tests` +- Create test classes matching the class under test (e.g., `CalculatorTests` for `Calculator`) +- Use SDK-style projects +- xUnit v3 test projects are **executable projects**, not class libraries. Set `Exe` in the `.csproj` +- Align target frameworks with xUnit v3 minimums: **net472+** or **net8+** + +### Package Selection + +| Runner integration | Required packages | +|--------------------|-------------------| +| **MTP-first** (default for new projects) | `xunit.v3` | +| **VSTest / Test Explorer** | `xunit.v3` + `xunit.runner.visualstudio` (3.x+) + `Microsoft.NET.Test.Sdk` | + +Run tests with: +- `dotnet test` — for test-runner integration (MTP or VSTest) +- `dotnet run` — to execute the test executable directly (xUnit v3 native) + +### Migrating from xUnit v2 + +| Old package | Action | +|-------------|--------| +| `xunit` | Replace with `xunit.v3` | +| `xunit.abstractions` | Remove from test projects | +| `xunit.console` | Remove (not supported in v3) | + +If not specified, default new projects to **xUnit v3** with **MTP**. + ## When to Use Invoke this skill when you: diff --git a/plugins/vuetify-components/README.md b/plugins/vuetify-components/README.md index b6e267f..99d8c9b 100644 --- a/plugins/vuetify-components/README.md +++ b/plugins/vuetify-components/README.md @@ -1,430 +1,33 @@ # Vuetify Components & Patterns Plugin -Professional Vue 3 and Vuetify best practices for building enterprise-grade UI components with accessibility, responsive design, and UX consistency at the core. +Vue 3 and Vuetify best practices for building professional UI components with accessibility and UX consistency at the core. ## Overview -This plugin provides comprehensive guidance on leveraging Vuetify 3 and Vue 3's Composition API to create professional, accessible, and maintainable user interfaces. It combines real-world patterns with WCAG accessibility standards and enterprise UX principles. - -## What This Plugin Covers - -### Vue 3 & Composition API - -- Modern reactive patterns using `ref()`, `reactive()`, and `computed()` -- Watch and watchers for side effects -- Custom composables for code reuse -- Lifecycle hooks in Composition API -- Performance optimization techniques - -### Vuetify Component Library - -- **Layout Components**: VContainer, VRow, VCol, VSheet, VNavigationDrawer, VAppBar -- **Data Display**: VDataTable, VList, VCard, VChip -- **Form Components**: VTextField, VSelect, VCheckbox, VRadio, VSwitch, VDatePicker -- **Dialog & Menu Patterns**: VDialog, VMenu, VBottomSheet -- **Typography & Icons**: Text hierarchy, icon usage, semantic HTML -- **Theming & Customization**: Light/dark modes, color palettes, custom themes - -### Professional Component Development - -- Component composition and nesting -- Prop design and slot patterns -- State management with `v-model` -- Error handling and validation -- Loading states and skeletons -- Responsive breakpoint strategies - -### Accessibility & UX - -- Web Accessibility (WCAG) standards -- Semantic HTML structure -- ARIA attributes and roles -- Keyboard navigation and focus management -- Color contrast and visual clarity -- Form accessibility with proper labels and error messages -- Screen reader support -- Testing accessibility with axe-core - -## Why Professional Vuetify Matters - -Using Vuetify properly ensures: - -- **Consistent UX**: Material Design principles across your entire application -- **Accessibility**: WCAG compliance out of the box, extended with best practices -- **Maintainability**: Clear component patterns reduce technical debt -- **Productivity**: Pre-built components let you focus on business logic -- **Professionalism**: Enterprise-grade UI that builds customer confidence -- **Performance**: Optimized rendering with Vue 3 reactivity +This plugin provides guidance on Vuetify 3 and Vue 3's Composition API for creating accessible, maintainable UIs. It directs Copilot to the authoritative Vuetify docs and enforces professional patterns. ## Quick Start -### Installation - -Install this plugin to your Copilot CLI: - ```bash copilot plugin install vuetify-components@IntelliPlugins ``` -### Common Component Patterns - -#### Text Input with Validation - -```vue - - - -``` - -#### Data Table with Sorting and Pagination - -```vue - - - -``` - -#### Dialog with Form Submission - -```vue - - - -``` - -#### Responsive Layout with Navigation - -```vue - - - -``` - -#### Composable for API Calls - -```typescript -import { ref, readonly } from "vue"; - -export const useFetch = (url: string) => { - const data = ref(null); - const isLoading = ref(false); - const error = ref(null); - - const fetch = async () => { - isLoading.value = true; - error.value = null; - try { - const response = await window.fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - data.value = await response.json(); - } catch (err) { - error.value = err instanceof Error ? err : new Error(String(err)); - } finally { - isLoading.value = false; - } - }; - - return { - data: readonly(data), - isLoading: readonly(isLoading), - error: readonly(error), - fetch, - }; -}; - -// Usage in component: -// const { data: users, isLoading, error, fetch } = useFetch('/api/users') -// onMounted(() => fetch()) -``` - -## Key Documentation Files +## Skills -### [Vuetify Components & Patterns](instructions/vuetify-components.md) +- **`scaffold-component`** — Generate a Vue 3 SFC with Vuetify components, props, emits, and accessibility +- **`use-composition-api`** — Reactive state, composables, lifecycle hooks with TypeScript +- **`add-form`** — Add a Vuetify form with built-in validation and submission handling +- **`add-accessibility`** — Audit and improve WCAG 2.1 AA compliance in Vuetify components -Detailed reference for all major Vuetify components, layout systems, theming, and composition strategies. This is your go-to guide for component-specific patterns and best practices. +## Reference -### [Vue 3 Composition API](instructions/vue3-composition.md) +Always refer to the official docs for the authoritative component API, props, slots, and events: -Modern Vue 3 reactive patterns, composables, lifecycle hooks, and performance optimization. Master the Composition API for cleaner, more reusable component code. - -### [Accessibility & UX Best Practices](instructions/accessibility-ux.md) - -WCAG compliance, semantic HTML, ARIA attributes, keyboard navigation, and inclusive design. Build UIs that work for everyone. +- **[Vuetify 3 Documentation](https://vuetifyjs.com/en/)** — component API reference, theming, layout +- **[Vue 3 Documentation](https://vuejs.org/)** — Composition API, reactivity, SFC guide +- **[WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)** — accessibility standards ## Related Plugins -- **[SOLID Principles & Architecture](https://intellitect.github.io/IntelliPlugins/plugins/solid-principles)** — Enterprise code quality and design patterns to structure your Vue applications professionally - **[Coalesce Accelerator](https://intellitect.github.io/IntelliPlugins/plugins/coalesce-accelerator)** — Full-stack data access patterns that complement your Vue UI - -## Vuetify Documentation - -For the authoritative Vuetify reference: - -- **[Vuetify 3 Official Docs](https://vuetifyjs.com/)** — Complete component API reference -- **[Material Design Guidelines](https://material.io/design)** — Design principles behind Vuetify components -- **[Vue 3 Official Docs](https://vuejs.org/)** — Comprehensive Vue 3 guide - -## Component Categories - -### Layout & Structure - -- VApp, VAppBar, VNavigationDrawer, VContainer, VRow, VCol -- VSheet, VMain, VFooter -- Grid system and responsive breakpoints - -### Data Display - -- VDataTable, VDataTableVirtual (large datasets) -- VList, VListItem, VListGroup -- VCard, VCardText, VCardActions -- VChip, VAvatar, VProgressLinear - -### User Input - -- VTextField, VTextarea, VSelect, VAutocomplete -- VCheckbox, VSwitch, VRadio, VRadioGroup -- VSlider, VRangeSlider, VCombobox -- VDatePicker, VTimePicker, VColorPicker - -### Dialogs & Overlays - -- VDialog, VMenu, VBottomSheet -- VTooltip, VPopover -- VSnackbar for notifications - -### Navigation - -- VTabs, VBreadcrumbs, VPagination -- VPaginationGroup - -### Feedback - -- VAlert, VBanner -- VProgressLinear, VProgressCircular -- VSkeletonLoader -- VSnackbar, VSpeedDial - -## Best Practices Summary - -1. **Always prioritize accessibility** — WCAG compliance isn't optional -2. **Use Composition API** — Modern, cleaner, more maintainable code -3. **Extract custom composables** — Reuse logic across components -4. **Validate early and often** — User input should be validated with clear feedback -5. **Design for responsive** — Mobile-first approach with proper breakpoints -6. **Test accessibility** — Use axe-core for automated accessibility testing -7. **Keep components focused** — Single responsibility makes them easier to maintain -8. **Document your components** — Props, slots, and events should be clear -9. **Manage state consistently** — Use `v-model`, `ref()`, and `computed()` appropriately -10. **Consider performance** — Use lazy loading, virtual scrolling for large datasets - -## Example Project Structure - -``` -src/ -├── components/ -│ ├── common/ -│ │ ├── AppBar.vue -│ │ ├── NavigationDrawer.vue -│ │ └── Dialogs.vue -│ ├── features/ -│ │ ├── UserTable.vue -│ │ ├── UserForm.vue -│ │ └── UserDialog.vue -│ └── shared/ -│ ├── LoadingSpinner.vue -│ ├── ErrorAlert.vue -│ └── ConfirmDialog.vue -├── composables/ -│ ├── useFetch.ts -│ ├── useForm.ts -│ ├── useDialog.ts -│ └── useLocalStorage.ts -├── types/ -│ ├── user.ts -│ ├── api.ts -│ └── form.ts -└── styles/ - ├── variables.ts - ├── theme.ts - └── breakpoints.ts -``` - -## Contributing - -This plugin is part of the IntelliPlugins marketplace. For updates and improvements, visit the [GitHub repository](https://github.com/IntelliTect/IntelliPlugins). - -## License - -MIT License — See LICENSE file in repository - ---- - -**Ready to build accessible, professional Vue 3 applications?** Start with the [Vuetify Components & Patterns guide](instructions/vuetify-components.md). +- **[SOLID Principles & Architecture](https://intellitect.github.io/IntelliPlugins/plugins/solid-principles)** diff --git a/plugins/vuetify-components/plugin.json b/plugins/vuetify-components/plugin.json index 6790973..579a3ab 100644 --- a/plugins/vuetify-components/plugin.json +++ b/plugins/vuetify-components/plugin.json @@ -2,7 +2,7 @@ "name": "vuetify-components", "displayName": "Vuetify Components & Patterns", "description": "Vue 3 and Vuetify best practices for building professional UI components with accessibility and UX consistency.", - "version": "1.0.0", + "version": "1.0.1", "publisher": "IntelliTect", "license": "MIT", "homepage": "https://intellitect.github.io/IntelliPlugins/plugins/vuetify-components", @@ -22,11 +22,6 @@ "responsive-design", "composition-api" ], - "categories": [ - "Framework", - "Frontend", - "UI/UX", - "Accessibility" - ], + "categories": ["Framework", "Frontend", "UI/UX", "Accessibility"], "skills": "skills/" } diff --git a/plugins/vuetify-components/skills/add-accessibility/SKILL.md b/plugins/vuetify-components/skills/add-accessibility/SKILL.md index 8cac523..b2f93f1 100644 --- a/plugins/vuetify-components/skills/add-accessibility/SKILL.md +++ b/plugins/vuetify-components/skills/add-accessibility/SKILL.md @@ -5,1127 +5,74 @@ description: Add WCAG-compliant accessibility and UX improvements to Vuetify com # Add Accessibility Skill -Audit and improve Vuetify Vue 3 components for WCAG compliance, keyboard navigation, ARIA labeling, color contrast, and overall UX quality. +Audit and improve Vuetify Vue 3 components for WCAG 2.1 AA compliance — ARIA labels, keyboard navigation, color contrast, and semantic HTML. ## When to Use -Invoke this skill when you: -- Need to audit a component for accessibility issues -- Are adding ARIA labels, roles, or keyboard navigation support -- Want to ensure color contrast and visual accessibility standards -- Need to improve UX consistency and Vuetify component usage +- Auditing a component for accessibility issues +- Adding ARIA labels, roles, or keyboard navigation +- Ensuring color contrast meets WCAG standards +- Improving UX consistency -# Accessibility & UX Best Practices +## Key Requirements -Build inclusive, professional user interfaces that work for everyone. Master WCAG accessibility standards, semantic HTML, keyboard navigation, and accessibility testing. +### Semantic HTML -## Table of Contents - -1. [Web Accessibility Fundamentals](#web-accessibility-fundamentals) -2. [Semantic HTML](#semantic-html) -3. [ARIA Attributes and Roles](#aria-attributes-and-roles) -4. [Keyboard Navigation](#keyboard-navigation) -5. [Color, Contrast, and Visual Clarity](#color-contrast-and-visual-clarity) -6. [Form Accessibility](#form-accessibility) -7. [Screen Reader Support](#screen-reader-support) -8. [Focus Management](#focus-management) -9. [Testing for Accessibility](#testing-for-accessibility) -10. [Accessible Components](#accessible-components) -11. [Common Patterns](#common-patterns) - ---- - -## Web Accessibility Fundamentals - -Web accessibility (a11y) ensures your application is usable by everyone, regardless of ability or device. This isn't optional — it's a legal requirement in many jurisdictions and a mark of professional quality. - -### Why Accessibility Matters - -- **Legal**: WCAG compliance is required by law in many countries -- **Business**: Accessible sites have better SEO and reach wider audiences -- **Ethical**: Everyone deserves access to digital services -- **Quality**: Accessibility practices improve design for everyone -- **Performance**: Accessible code is often better optimized - -### WCAG Standards - -The Web Content Accessibility Guidelines (WCAG) 2.1 are the gold standard. Compliance levels: - -- **Level A**: Basic accessibility (minimum) -- **Level AA**: Enhanced accessibility (recommended for most sites) -- **Level AAA**: Expert accessibility (ambitious, some features conflict with AA) - -Target **WCAG 2.1 Level AA** for professional applications. - -### Four Principles: POUR - -``` -Perceivable - Users can perceive content (not invisible) -Operable - Users can navigate and use controls (keyboard friendly) -Understandable - Users understand content and how to use it -Robust - Works with current and future technologies (valid HTML) -``` - ---- - -## Semantic HTML - -Use HTML semantically to provide meaning to screen readers and browsers. - -### Page Structure - -```html - -
- -
-
-
Content
- -
- - - -
- -
-
-
Content
- -
-
Footer
-``` - -### Semantic Elements - -```html - -
Page or section header
-
Page or section footer
-
Main content (one per page)
- - - - - -
Thematic grouping of content
-
Self-contained content (blog post, news article)
- - - -

Page title (only one per page)

-

Major section

-

Subsection

- - - -
    -
  • Unordered list item
  • -
- - -Important text -Emphasized text -Highlighted text -Code snippet -``` - -### Headings Hierarchy - -```vue - -

Page Title

-

Section Title

-

Content...

- - -

Page Title

-

Section Title

-

Content...

- - -

Page Title

-

Section 1

-

Content...

-

Section 2

-

Content...

-

Subsection 2.1

-

Content...

-``` - -### Buttons vs Links - -```html - - - - - -Users -Settings - - -Delete - - - - - - -Users -``` - ---- - -## ARIA Attributes and Roles - -ARIA (Accessible Rich Internet Applications) adds semantic meaning to dynamic content. - -### ARIA Roles - -```html - -
- An error has occurred -
- - -
- -
- - -
- - -
- - -
- -
- - -
-

Confirm Action

- -
-``` - -### ARIA Properties - -```html - - - - -

Users

- - -
- - - -
Password must be 8+ characters
- - - - - -
- Loading... -
- - - - - - -Invalid email format -``` - -### ARIA States - -```html - -
Disabled Action
- - - - - - - -
- -
- - -
Checked Item
- - - - -``` - ---- - -## Keyboard Navigation - -All interactive elements must be accessible via keyboard alone. - -### Tab Order - -```html - - - -Home - - -
- Clickable div -
- - - - - - - -``` - -### Keyboard Shortcuts +Use Vuetify's semantic props (`tag`) and HTML elements to convey meaning: ```vue - - - - - - - - - + + +

Results

+
+
``` -### Skip Links +### ARIA Labels -Help keyboard users skip repetitive content. +Always label interactive elements that lack visible text: ```vue - + + - + + ``` ---- - -## Color, Contrast, and Visual Clarity +### Keyboard Navigation -Ensure sufficient color contrast and don't rely on color alone. +- Vuetify handles most keyboard nav automatically — do not override default focus behavior +- Modals must trap focus: use `v-dialog` (built-in) rather than custom overlays +- After closing a dialog, return focus to the trigger element ### Color Contrast -```html - - +- Text on backgrounds must meet 4.5:1 ratio (WCAG AA) +- Never rely on color alone to convey state — pair with icon or text label - -

- Light gray on white - too low contrast -

- - -

- Dark gray on white - 7:1 contrast -

- - -``` - -### Don't Rely on Color Alone +### Form Accessibility ```vue - -
Success
-
Error
- - -
- - Success -
- -
- - Error -
- - - - - - - -
AvailableBooked
-``` - -### Focus Indicators - -```css -/* Always provide visible focus indicators */ -button:focus, -a:focus, -input:focus { - outline: 2px solid #1976D2; - outline-offset: 2px; -} - -/* Don't remove outlines! */ -button:focus { - /* Never do this: */ - /* outline: none; */ -} - -/* Custom focus style (keep it visible) */ -button:focus-visible { - outline: 3px solid #FFD700; - outline-offset: 2px; -} -``` - -### Visual Hierarchy - -```html - -

Primary Heading (28px, bold)

-

Secondary Heading (24px, bold)

-

Body text (16px, regular)

-Caption text (14px, lighter) - - -

- Adequate line height and paragraph spacing aid readability -

-``` - ---- - -## Form Accessibility - -Forms are critical for accessibility. Proper labeling and error handling are essential. - -### Proper Form Labels - -```vue - - - - - - - - - + :error-messages="errors" required + aria-required="true" /> - - - - - -``` - -### Error Messages and Validation - -```vue - - - - -``` - -### Form Groups and Fieldsets - -```html - -
- Contact Preferences - - - - -
- - -
-

Contact Preferences

- -
-``` - -### Form Submission - -```vue - - - -``` - ---- - -## Screen Reader Support - -Make your application understandable to screen reader users. - -### Meaningful Link Text - -```html - -Click here -Read more - - -View John Doe's profile -Download accessibility guide (PDF) - - - - - -``` - -### Image Alt Text - -```html - - - - -Sales chart showing 20% increase in Q4 - - - - Company logo home - - - -System architecture diagram -
- -
-``` - -### Tables - -```html - - - - - - - - - - - - - - - - - - - - - - - - -
Sales by Region (Q4 2023)
RegionSalesGrowth
North America$1.2M15%
Europe$800K12%
-``` - -### Lists - -```html - -
    -
  • First item
  • -
  • Second item
  • -
- - -
-
First item
-
Second item
-
-``` - ---- - -## Focus Management - -Manage focus for a smooth, predictable experience. - -### Auto-Focus on Dialog - -```vue - - - -``` - -### Focus Trap in Modal - -```typescript -// composables/useFocusTrap.ts -import { ref, onMounted, onUnmounted } from 'vue' - -export const useFocusTrap = (containerRef: Ref) => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key !== 'Tab' || !containerRef.value) return - - const focusable = containerRef.value.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ) - const first = focusable[0] as HTMLElement - const last = focusable[focusable.length - 1] as HTMLElement - - if (e.shiftKey && document.activeElement === first) { - e.preventDefault() - last.focus() - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault() - first.focus() - } - } - - onMounted(() => { - containerRef.value?.addEventListener('keydown', handleKeyDown) - }) - - onUnmounted(() => { - containerRef.value?.removeEventListener('keydown', handleKeyDown) - }) -} - -// Usage -const modalRef = ref(null) -useFocusTrap(modalRef) -``` - ---- - -## Testing for Accessibility - -### Automated Testing with axe-core - -```typescript -// Example: Cypress + axe-core -import { injectAxe, checkA11y } from 'axe-playwright' - -test('Page should be accessible', async ({ page }) => { - await page.goto('/') - - // Inject axe testing engine - await injectAxe(page) - - // Run accessibility checks - await checkA11y(page) -}) -``` - -### Manual Testing Checklist - -```markdown -## Accessibility Testing Checklist - -### Keyboard Navigation -- [ ] All interactive elements are reachable via Tab -- [ ] Tab order is logical and matches visual order -- [ ] No keyboard traps (can't Tab out) -- [ ] Enter/Space/Escape work as expected - -### Screen Reader (NVDA, JAWS, VoiceOver) -- [ ] Page structure makes sense when read aloud -- [ ] Form labels are announced with inputs -- [ ] Errors are announced as alerts -- [ ] Images have meaningful alt text -- [ ] Links have descriptive text - -### Vision -- [ ] Text has sufficient contrast (4.5:1 minimum) -- [ ] Focus indicators are clearly visible -- [ ] Content doesn't rely on color alone -- [ ] Text is readable at 200% zoom - -### Motor -- [ ] All interactions work via keyboard -- [ ] Click targets are large enough (44x44px minimum) -- [ ] No time-dependent interactions -- [ ] No flashing/blinking content (more than 3x per second) - -### Cognition -- [ ] Language is clear and simple -- [ ] Navigation is consistent -- [ ] Error messages are helpful -- [ ] Instructions are clear ``` -### WAVE Browser Extension - -Use the WAVE (Web Accessibility Evaluation Tool) browser extension to identify accessibility issues: - -1. Install WAVE from your browser's extension store -2. Click the WAVE icon on any page -3. Review errors, warnings, and features -4. Fix identified issues - -### Lighthouse in Chrome DevTools - -``` -1. Open Chrome DevTools (F12) -2. Go to Lighthouse tab -3. Select "Accessibility" category -4. Run audit -5. Review findings and recommendations -``` - ---- - -## Accessible Components - -### Accessible Alert - -```vue - - - -``` - -### Accessible Dialog - -```vue - - - -``` - -### Accessible Form - -```vue - - - -``` - ---- - -## Common Patterns - -### Loading State with Accessible Feedback - -```vue - - - -``` - -### Accessible Data Table - -```vue - -``` - ---- - -## Best Practices Summary - -1. **Always use semantic HTML** — It's the foundation of accessibility -2. **Test with real assistive technology** — NVDA, JAWS, VoiceOver -3. **Provide alternative text** — For images, icons, and visual information -4. **Ensure color contrast** — Minimum 4.5:1 for normal text (WCAG AA) -5. **Make everything keyboard accessible** — Tab, Enter, Escape should work -6. **Use ARIA wisely** — It supplements, doesn't replace semantic HTML -7. **Test with axe-core** — Automated accessibility scanning -8. **Focus management** — Dialogs, modals, and single-page apps need careful focus handling -9. **Clear error messages** — Tell users what's wrong and how to fix it -10. **Include skip links** — Help keyboard users navigate efficiently - ---- - -## Additional Resources - -- **[WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)** — Official accessibility standards -- **[MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)** — Comprehensive web accessibility guide -- **[WebAIM](https://webaim.org/)** — Web accessibility articles and tools -- **[ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)** — Official ARIA design patterns -- **[axe DevTools](https://www.deque.com/axe/devtools/)** — Browser extension for accessibility testing -- **[WAVE Tool](https://wave.webaim.org/)** — Website accessibility evaluation - ---- +- [ ] All images have `alt` text (or `alt=""` if decorative) +- [ ] Icon-only buttons have `aria-label` +- [ ] Form fields have visible labels, not just placeholder +- [ ] Error messages are associated with their field +- [ ] Focus order is logical (matches visual order) +- [ ] No content is keyboard-inaccessible -**Congratulations!** You now have the knowledge to build professional, accessible Vue 3 applications with Vuetify. Remember: accessibility isn't a feature — it's a fundamental requirement for quality software. +## Reference -**Next Steps:** -1. Review [Vuetify Components & Patterns](vuetify-components.md) for component-specific accessibility patterns -2. Check out the [Vue 3 Composition API](vue3-composition.md) guide for modern reactive patterns -3. Start building with accessibility in mind from day one +- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/) +- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) +- [Vuetify Accessibility docs](https://vuetifyjs.com/en/features/accessibility/) diff --git a/plugins/vuetify-components/skills/scaffold-component/SKILL.md b/plugins/vuetify-components/skills/scaffold-component/SKILL.md index bd3c084..ba12a6a 100644 --- a/plugins/vuetify-components/skills/scaffold-component/SKILL.md +++ b/plugins/vuetify-components/skills/scaffold-component/SKILL.md @@ -5,30 +5,27 @@ description: Scaffold a new Vuetify Vue 3 component using the Composition API wi # Scaffold Component Skill -Generate a well-structured Vuetify Vue 3 single-file component (SFC) using the Composition API, following accessibility and UX best practices. +Generate a well-structured Vuetify Vue 3 SFC using the Composition API. ## When to Use -Invoke this skill when you need to: - Create a new reusable UI component -- Add a page-level component that wraps Vuetify layout components +- Add a page-level component wrapping Vuetify layout components - Build a data display component (table, card, list) - Create a modal or dialog component ## Required Information -Before scaffolding, provide: 1. **Component name** — PascalCase (e.g., `UserCard`, `InvoiceTable`, `ConfirmDialog`) 2. **Component purpose** — what data it displays or action it performs -3. **Props** — inputs from parent (names, types, whether required) -4. **Emits** — events the component raises to parent (e.g., `update:modelValue`, `confirm`) +3. **Props** — inputs from parent (names, types, required vs optional) +4. **Emits** — events raised to parent (e.g., `update:modelValue`, `confirm`) ## SFC Structure ```vue -``` - -**Responsive Breakpoints**: -``` -xs: 0px (extra small) -sm: 600px (small) -md: 960px (medium) -lg: 1264px (large) -xl: 1904px (extra large) -``` - -**VCol Props**: -- `cols`: Default columns (1-12) at xs breakpoint -- `sm`, `md`, `lg`, `xl`: Columns at specific breakpoints -- `offset-*`: Offset columns at each breakpoint -- `order-*`: Reorder columns with flexbox - -### VSheet - -Flexible container for grouping content with consistent elevation and styling. - -```vue - - - - - rounded - color="primary" - class="pa-4 ma-2" -/> -``` - -**Usage Guidelines**: -- Use `VSheet` as a flexible container when `VCard` is overkill -- Elevation (shadow) for depth and visual hierarchy -- Combine with Vuetify spacing utilities (pa-*, ma-*, py-*, px-*) - -### VNavigationDrawer - -Persistent or temporary navigation sidebar. - -```vue - - - -``` - -**Props**: -- `v-model`: Control open/closed state -- `rail`: Collapse to icon-only mode -- `permanent`: Always visible (on large screens) -- `app`: Take full height including AppBar -- `width`: Custom drawer width (default 256px) - -### VAppBar - -Top application bar with title, actions, and navigation. - -```vue - - - -``` - -**Props**: -- `app`: Fix to top of screen -- `color`: Background color -- `dark`/`light`: Text color scheme -- `flat`: Remove elevation shadow -- `border`: Add bottom border instead of shadow - ---- - -## Data Display Components - -### VDataTable - -Powerful table component with sorting, filtering, and pagination. - -```vue - - - -``` - -**Key Features**: -- Two-way binding with `v-model:page` and `v-model:items-per-page` -- Search filtering across all columns -- Sortable columns (customize per column) -- Custom item rendering with scoped slots -- Loading state with spinner overlay - -### VList - -Flexible list component with items, groups, and avatars. - -```vue - - - -``` - -### VCard - -Container for content with header, text, and actions. - -```vue - -``` - -**Card Structure**: -- `VCardTitle`: Card heading -- `VCardSubtitle`: Optional subtitle -- `VCardText`: Main content area -- `VCardActions`: Footer with buttons -- Combine with `VImg`, `VDivider` for complex layouts - -### VChip - -Small, compact component for tags, categories, or selections. - -```vue - - - -``` - ---- - -## Form Components - -### VTextField - -Single-line text input with validation and icons. - -```vue - - - -``` - -**Props**: -- `type`: 'text', 'email', 'password', 'number', 'search', 'url', 'tel' -- `label`: Field label (floats on focus) -- `placeholder`: Input placeholder text -- `prepend-icon`/`append-icon`: Icons before/after input -- `clearable`: Show clear button when value exists -- `rules`: Array of validation functions -- `error-messages`: Display validation errors -- `hint`: Helper text below field -- `persistent-hint`: Always show hint (not just on focus) -- `readonly`: Prevent user input -- `disabled`: Disable field completely -- `variant`: 'outlined', 'filled', 'underlined', 'plain' -- `dense`: Smaller height -- `rounded`: Rounded corners - -### VSelect - -Dropdown selection with multiple choices. - -```vue - - - -``` - -**Props**: -- `multiple`: Allow multiple selections -- `chips`: Display selections as chips -- `search`: Enable search/filter -- `item-title`: Property name for display text -- `item-value`: Property name for actual value -- `clearable`: Show clear button -- `disabled`: Disable field - -### VCheckbox - -Checkbox for boolean input. - -```vue - - - -``` - -### VRadio & VRadioGroup - -Radio buttons for single selection from multiple options. - -```vue - - - -``` - -### VSwitch - -Toggle switch for boolean values. - -```vue - - - -``` - ---- - -## Dialog & Menu Patterns - -### VDialog - -Modal dialog for forms, confirmations, or detailed content. - -```vue - - - -``` - -**Props**: -- `max-width`: Maximum width (e.g., '500', '600', 'lg') -- `width`: Explicit width -- `fullscreen`: Full screen dialog -- `scrollable`: Scrollable content area -- `persistent`: Clicking outside doesn't close -- `transition`: Animation type - -### VMenu - -Context menu or dropdown menu. - -```vue - - - -``` - ---- - -## Typography & Icons - -### Text Hierarchy - -Use text classes for consistent typography. - -```vue - -``` - -### Icons with VIcon - -Material Design Icons integration. - -```vue - - - - - - -``` - ---- - -## Theming & Customization - -### Vuetify Theme Setup - -```typescript -// vuetify.ts -import { createVuetify } from 'vuetify' -import * as components from 'vuetify/components' -import * as directives from 'vuetify/directives' -import { aliases, mdi } from 'vuetify/iconsets/mdi-svg' -import '@mdi/js' - -export default createVuetify({ - components, - directives, - icons: { - defaultSet: 'mdi', - aliases, - sets: { mdi }, - }, - theme: { - defaultTheme: 'light', - themes: { - light: { - colors: { - primary: '#1976D2', - secondary: '#424242', - success: '#4CAF50', - warning: '#FFC107', - error: '#F44336', - info: '#2196F3', - }, - }, - dark: { - colors: { - primary: '#BB86FC', - secondary: '#03DAC6', - success: '#66BB6A', - warning: '#FFA726', - error: '#EF5350', - info: '#42A5F5', - }, - }, - }, - }, -}) -``` - -### Custom Color Palette - -```vue - -``` - -### Responsive Breakpoints - -```typescript -// Access breakpoints in components -import { useDisplay } from 'vuetify' - -const { xs, sm, md, lg, xl, mdAndUp, smAndUp } = useDisplay() - -// In template -v-if="mdAndUp" // Show on md and larger -v-if="smAndDown" // Show on sm and smaller -``` - ---- - -## Component Composition - -### Composite Components - -Create reusable component combinations. - -```vue - - - - - - - -``` - -### Wrapper Components - -Wrap Vuetify components with custom styling and behavior. - -```vue - - - - - - - -``` - ---- - -## Props & Slots - -### Working with Props - -```vue - - - -``` - -### Named Slots - -Most Vuetify components support multiple slots for customization. - -```vue - -``` - ---- - -## Advanced Patterns - -### Infinite Scroll with VList - -```vue - - - -``` - -### Expandable Rows in DataTable - -```vue - -``` - -### Modal with Scrollable Content - -```vue - -``` - ---- - -## Key Takeaways +- Prefer `defineProps()` TypeScript generics over `withDefaults` +- Use `defineModel()` for two-way binding (Vue 3.4+) -1. **Use responsive props** (cols, sm, md, lg) for mobile-first design -2. **Leverage slots** for customization without overriding components -3. **Combine validation** with clear error messages -4. **Use theming** for consistent visual design -5. **Extract composables** for reusable logic -6. **Test accessibility** from the start (next guide covers this in depth) -7. **Follow Material Design** principles for consistency -8. **Consider performance** with virtual scrolling and lazy loading +## Reference -**Next:** Explore [Vue 3 Composition API patterns](vue3-composition.md) for modern, maintainable components. +- [Vuetify Component API](https://vuetifyjs.com/en/components/all/) — full props, slots, events for all components +- [Vue 3 SFC docs](https://vuejs.org/guide/scaling-up/sfc.html) diff --git a/plugins/vuetify-components/skills/use-composition-api/SKILL.md b/plugins/vuetify-components/skills/use-composition-api/SKILL.md index f2141cd..7c69bb8 100644 --- a/plugins/vuetify-components/skills/use-composition-api/SKILL.md +++ b/plugins/vuetify-components/skills/use-composition-api/SKILL.md @@ -5,1250 +5,78 @@ description: Build Vue 3 components using the Composition API with TypeScript an # Use Composition API Skill -Build Vue 3 single-file components using the Composition API with TypeScript, covering reactive state, composables, lifecycle hooks, and performance optimization. +Build Vue 3 SFCs using the Composition API with TypeScript — reactive state, composables, lifecycle hooks. ## When to Use -Invoke this skill when you: -- Are building a new Vue 3 component and want to use the Composition API correctly -- Need to refactor an Options API component to Composition API -- Want guidance on reactive state, composables, or lifecycle patterns -- Are implementing custom composables for shared logic +- Building a new Vue 3 component +- Refactoring an Options API component to Composition API +- Implementing custom composables for shared logic -# Vue 3 Composition API Guide - -Master modern Vue 3 patterns with the Composition API. Learn reactive programming, composables, and performance optimization for professional, maintainable applications. - -## Table of Contents - -1. [Composition API Fundamentals](#composition-api-fundamentals) -2. [Reactive State Management](#reactive-state-management) -3. [Computed Properties](#computed-properties) -4. [Watchers and Effects](#watchers-and-effects) -5. [Custom Composables](#custom-composables) -6. [Lifecycle Hooks](#lifecycle-hooks) -7. [Template Refs](#template-refs) -8. [Async Operations](#async-operations) -9. [Performance Optimization](#performance-optimization) -10. [Common Patterns](#common-patterns) - ---- - -## Composition API Fundamentals - -The Composition API is Vue 3's modern way to organize component logic. Instead of organizing by option (data, methods, computed), you organize by feature. - -### Why Use Composition API? - -- **Better Code Organization**: Group related logic together -- **Easier Code Reuse**: Extract logic into composables -- **Better TypeScript Support**: Full type inference -- **Cleaner Scaling**: Large components remain manageable -- **No Magic**: Explicit data flow and dependencies - -### Setup Function - -The ` -``` - -Compare with Options API: - -```vue - - -``` - ---- - -## Reactive State Management - -### ref() — Primitive Values - -Use `ref()` to make primitive values reactive. - -```vue - - - -``` - -### reactive() — Objects - -Use `reactive()` for objects and complex state. - -```vue - - - -``` +## Core Patterns ### ref() vs reactive() ```typescript -// ref() - for primitives, but also works for objects -const count = ref(0) // Good: primitive -const user = ref({}) // Works, but... -console.log(user.value.name) // Need .value everywhere - -// reactive() - for objects, no .value needed -const user = reactive({}) -console.log(user.name) // Clean: no .value - -// Best practice: -// - Use ref() for primitives and when you need reassignment -// - Use reactive() for objects with many properties -// - Combine them in a pattern called "composition" - -const user = ref(null) // Might be null -const settings = reactive({ // Complex object +const count = ref(0) // primitives, nullable refs +const settings = reactive({ // objects with many properties theme: 'dark', notifications: true, }) ``` -### Reactive Destructuring with toRefs() - -```vue - - - -``` - ---- - -## Computed Properties - -Derived state that updates automatically when dependencies change. - -### Basic Computed - -```vue - - - -``` - -### Computed with Getter and Setter - -```vue - - - -``` - -### Computed Performance - -```vue - -``` - ---- - -## Watchers and Effects - -Monitor reactive state and react to changes. - -### watch() — Track Specific Values - -```vue - -``` - -### watch() with Options +### computed() ```typescript -import { ref, watch } from 'vue' - -const user = ref({ name: 'John', age: 30 }) - -// Deep watch: track nested property changes -watch( - user, - (newUser) => { - console.log('User changed:', newUser) - }, - { deep: true } // Enable deep watching -) - -// Modify nested property -user.value.age = 31 // This will trigger the watch - -// Immediate: run callback on first setup -watch( - user, - (newUser) => { - console.log('Initial and on change:', newUser) - }, - { immediate: true } // Runs immediately with current value -) - -// Flush timing -watch( - user, - () => { - console.log('Callback executed') - }, - { flush: 'post' } // After component update (default) - // flush: 'pre' // Before component update - // flush: 'sync' // Synchronously (rarely needed) -) +const fullName = computed(() => `${firstName.value} ${lastName.value}`) ``` -### watchEffect() — Automatic Dependency Tracking - -```vue - -``` - -### Cleanup in Watchers +### watch() ```typescript -import { ref, watch } from 'vue' - -const query = ref('') - -watch(query, async (newQuery, oldQuery, onCleanup) => { - let isActive = true - - // Register cleanup callback - onCleanup(() => { - isActive = false - console.log('Previous request cancelled') - }) - - // Simulate API call - const response = await fetch(`/api/search?q=${newQuery}`) - - // Only use response if not cleaned up - if (isActive) { - console.log('Using response:', response) - } +watch(searchQuery, async (newVal) => { + results.value = await fetchResults(newVal) }) - -// When query changes again, cleanup runs before new callback -// This prevents race conditions with async operations ``` ---- - -## Custom Composables - -Extract reusable logic into composable functions. - -### Basic Composable Pattern +### Custom Composable ```typescript -// composables/useCounter.ts -import { ref, computed } from 'vue' - -export const useCounter = (initialValue: number = 0) => { - const count = ref(initialValue) - - const increment = () => count.value++ - const decrement = () => count.value-- - const reset = () => count.value = initialValue - - const isPositive = computed(() => count.value > 0) - - return { - count, - increment, - decrement, - reset, - isPositive, - } -} - -// Usage in component -import { useCounter } from '@/composables/useCounter' - -export default { - setup() { - const { count, increment, decrement } = useCounter(10) - return { count, increment, decrement } - }, -} -``` - -### Composable with Lifecycle - -```typescript -// composables/useFetch.ts -import { ref, readonly, onMounted } from 'vue' - -export const useFetch = (url: string) => { - const data = ref(null) - const error = ref(null) +export function useItems() { + const items = ref([]) const isLoading = ref(false) + const error = ref(null) - const fetch = async () => { + async function load() { isLoading.value = true - error.value = null try { - const response = await window.fetch(url) - if (!response.ok) throw new Error(`HTTP ${response.status}`) - data.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err : new Error(String(err)) + items.value = await api.getItems() + } catch (e) { + error.value = e instanceof Error ? e : new Error(String(e)) } finally { isLoading.value = false } } - // Auto-fetch on mount (optional) - onMounted(() => fetch()) - - return { - data: readonly(data), - error: readonly(error), - isLoading: readonly(isLoading), - fetch, - } -} - -// Usage -const { data: users, isLoading, error, fetch } = useFetch('/api/users') - -// Manually trigger fetch -await fetch() -``` - -### Composable for Forms - -```typescript -// composables/useForm.ts -import { ref, reactive, computed } from 'vue' - -export interface FormOptions { - initialValues: T - onSubmit: (values: T) => Promise -} - -export const useForm = >({ - initialValues, - onSubmit, -}: FormOptions) => { - const values = reactive({ ...initialValues }) - const errors = reactive>({}) - const touched = reactive>({}) - const isSubmitting = ref(false) - - const isDirty = computed(() => { - return Object.entries(values).some( - ([key, value]) => value !== initialValues[key as keyof T] - ) - }) - - const isValid = computed(() => Object.keys(errors).length === 0) - - const setFieldError = (field: keyof T, message: string) => { - errors[field as string] = message - } - - const clearFieldError = (field: keyof T) => { - delete errors[field as string] - } - - const setFieldTouched = (field: keyof T) => { - touched[field as string] = true - } - - const setValues = (newValues: Partial) => { - Object.assign(values, newValues) - } - - const reset = () => { - Object.assign(values, initialValues) - Object.keys(errors).forEach(key => delete errors[key]) - Object.keys(touched).forEach(key => delete touched[key]) - } - - const submit = async () => { - isSubmitting.value = true - try { - await onSubmit(values) - } finally { - isSubmitting.value = false - } - } - - return { - values, - errors, - touched, - isDirty, - isValid, - isSubmitting, - setFieldError, - clearFieldError, - setFieldTouched, - setValues, - reset, - submit, - } + return { items: readonly(items), isLoading: readonly(isLoading), error: readonly(error), load } } - -// Usage -const { values, errors, submit, reset } = useForm({ - initialValues: { email: '', password: '' }, - async onSubmit(values) { - await api.login(values) - }, -}) ``` -### Composable with Local Storage +### Lifecycle Hooks ```typescript -// composables/useLocalStorage.ts -import { ref, watch, Ref } from 'vue' - -export const useLocalStorage = (key: string, initialValue: T): Ref => { - // Try to load from localStorage - const stored = localStorage.getItem(key) - const data = ref(stored ? JSON.parse(stored) : initialValue) - - // Watch for changes and update localStorage - watch( - data, - (newValue) => { - localStorage.setItem(key, JSON.stringify(newValue)) - }, - { deep: true } - ) - - return data -} - -// Usage -const theme = useLocalStorage('theme', 'light') -theme.value = 'dark' // Automatically saved to localStorage +onMounted(() => load()) +onUnmounted(() => cleanup()) ``` ---- - -## Lifecycle Hooks +## Guidelines -Execute code at specific points in component lifecycle. - -### Common Lifecycle Hooks - -```vue - -``` - -### Lifecycle Example: Timer - -```vue - - - -``` +- Prefer `ref()` for primitives; `reactive()` for complex objects +- Expose readonly state from composables to prevent external mutation +- Clean up watchers and listeners in `onUnmounted` +- Name composables `useXxx` -### Lifecycle Example: Event Listeners - -```typescript -import { onMounted, onUnmounted } from 'vue' - -const handleResize = () => { - console.log('Window resized:', window.innerWidth) -} - -onMounted(() => { - window.addEventListener('resize', handleResize) -}) - -onUnmounted(() => { - window.removeEventListener('resize', handleResize) -}) -``` - ---- - -## Template Refs - -Access DOM elements directly. - -### Basic Template Ref - -```vue - - - -``` - -### Component Refs - -```vue - - - - - - - - - -``` - -### Multiple Element Refs - -```vue - - - -``` - ---- - -## Async Operations - -Handling promises and API calls in Composition API. - -### Basic Async in Composable - -```typescript -import { ref } from 'vue' - -export const useFetchUser = (userId: number) => { - const user = ref(null) - const loading = ref(false) - const error = ref(null) - - const fetchUser = async () => { - loading.value = true - error.value = null - try { - const response = await fetch(`/api/users/${userId}`) - if (!response.ok) throw new Error('Failed to fetch user') - user.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err.message : 'Unknown error' - } finally { - loading.value = false - } - } - - return { user, loading, error, fetchUser } -} -``` - -### Async in Component - -```vue - - - -``` - -### Handling Race Conditions - -```typescript -import { ref, watch } from 'vue' - -export const useSearch = () => { - const query = ref('') - const results = ref([]) - const loading = ref(false) - - watch(query, async (newQuery, oldQuery, onCleanup) => { - if (!newQuery) { - results.value = [] - return - } - - loading.value = true - let isActive = true - - // Cleanup: mark as inactive if component unmounts or query changes - onCleanup(() => { - isActive = false - }) - - try { - const response = await fetch(`/api/search?q=${newQuery}`) - const data = await response.json() - - // Only use result if request is still active - if (isActive) { - results.value = data - } - } finally { - if (isActive) { - loading.value = false - } - } - }) - - return { query, results, loading } -} -``` - ---- - -## Performance Optimization - -### Lazy Load Composables - -```typescript -// Don't import all composables at top -import { ref } from 'vue' - -export const useLazyData = async () => { - // Heavy computation or import happens on-demand - const { complexCalculation } = await import('./heavyModule') - return { - result: ref(complexCalculation()), - } -} - -// Usage -const { result } = await useLazyData() -``` - -### Memoization with Computed - -```typescript -import { ref, computed } from 'vue' - -const items = ref([1, 2, 3, 4, 5]) - -// Expensive computation -const doubled = computed(() => { - console.log('Computing doubled...') - return items.value.map(n => n * 2) -}) - -// Memoized: only recalculates when items changes -doubled.value // Logs "Computing doubled..." -doubled.value // Uses cached result (no log) -items.value = [1, 2, 3, 4, 5] // Same content, same reference -doubled.value // Uses cached result (no log) -``` - -### Shallow Refs for Large Objects - -```typescript -import { shallowRef } from 'vue' - -const largeObject = shallowRef({ - data: /* huge data structure */ -}) - -// Vue only tracks reference change, not nested changes -// Efficient for large objects that don't change frequently -largeObject.value = { data: newData } // Triggers update -largeObject.value.data.prop = 'new' // Does NOT trigger update -``` - -### Unref for Flexible Parameters - -```typescript -import { ref, unref, isRef } from 'vue' - -export const useDoubleValue = (value: any) => { - // unref extracts value from ref or returns as-is - const resolved = unref(value) - return resolved * 2 -} - -useDoubleValue(5) // 10 -useDoubleValue(ref(5)) // 10 (unref extracts the 5) -``` - ---- - -## Common Patterns - -### Modal/Dialog Composable - -```typescript -// composables/useModal.ts -import { ref } from 'vue' - -export const useModal = () => { - const isOpen = ref(false) - - const open = () => { - isOpen.value = true - } - - const close = () => { - isOpen.value = false - } - - return { isOpen, open, close } -} - -// Usage -const { isOpen, open, close } = useModal() -``` - -### Toggle Composable - -```typescript -// composables/useToggle.ts -import { ref } from 'vue' - -export const useToggle = (initialValue: boolean = false) => { - const value = ref(initialValue) - - const toggle = () => { - value.value = !value.value - } - - const setTrue = () => { - value.value = true - } - - const setFalse = () => { - value.value = false - } - - return { value, toggle, setTrue, setFalse } -} - -// Usage -const { value: isDarkMode, toggle: toggleDarkMode } = useToggle() -``` - -### Throttle/Debounce - -```typescript -// composables/useDebounce.ts -import { ref, watch } from 'vue' - -export const useDebounce = (value: Ref, delay: number = 300): Ref => { - const debouncedValue = ref(value.value) - let timeout: NodeJS.Timeout - - watch(value, (newValue) => { - clearTimeout(timeout) - timeout = setTimeout(() => { - debouncedValue.value = newValue - }, delay) - }) - - return debouncedValue -} - -// Usage -const searchQuery = ref('') -const debouncedQuery = useDebounce(searchQuery, 500) - -watch(debouncedQuery, (query) => { - // API call with debounced query -}) -``` - -### Keyboard Shortcuts - -```typescript -// composables/useKeyboard.ts -import { onMounted, onUnmounted } from 'vue' - -export const useKeyboard = (key: string, callback: () => void) => { - const handler = (event: KeyboardEvent) => { - if (event.key === key) { - callback() - } - } - - onMounted(() => { - window.addEventListener('keydown', handler) - }) - - onUnmounted(() => { - window.removeEventListener('keydown', handler) - }) -} - -// Usage -useKeyboard('Escape', () => closeModal()) -useKeyboard('Enter', () => submit()) -``` - -### Responsive/Breakpoint Detection - -```typescript -// composables/useBreakpoint.ts -import { ref, onMounted, onUnmounted } from 'vue' - -export const useBreakpoint = () => { - const width = ref(0) - const isMobile = ref(false) - const isTablet = ref(false) - const isDesktop = ref(false) - - const updateBreakpoint = () => { - width.value = window.innerWidth - isMobile.value = width.value < 600 - isTablet.value = width.value >= 600 && width.value < 1024 - isDesktop.value = width.value >= 1024 - } - - onMounted(() => { - updateBreakpoint() - window.addEventListener('resize', updateBreakpoint) - }) - - onUnmounted(() => { - window.removeEventListener('resize', updateBreakpoint) - }) - - return { width, isMobile, isTablet, isDesktop } -} - -// Usage -const { isMobile, isTablet, isDesktop } = useBreakpoint() -``` - ---- - -## Type-Safe Composables - -### Generic Composable - -```typescript -// composables/useList.ts -export const useList = ( - initialItems: T[] = [] -) => { - const items = ref(initialItems) - - const add = (item: T) => { - items.value.push(item) - } - - const remove = (id: T['id']) => { - const index = items.value.findIndex(item => item.id === id) - if (index > -1) { - items.value.splice(index, 1) - } - } - - const update = (id: T['id'], updates: Partial) => { - const item = items.value.find(item => item.id === id) - if (item) { - Object.assign(item, updates) - } - } - - return { items, add, remove, update } -} - -// Usage with type -interface User { - id: number - name: string - email: string -} - -const { items: users, add, remove } = useList([ - { id: 1, name: 'John', email: 'john@example.com' }, -]) -``` - ---- - -## Best Practices - -1. **Use `