From df9bce64f73216b0b85b1a2414c4c3580c4d5f83 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Thu, 4 Jun 2026 17:11:11 +0200 Subject: [PATCH] Aggiunte skills per agente --- .github/skills/blazor-specialist/SKILL.md | 822 ++++++++++++++++++++++ .github/skills/frontend-design/SKILL.md | 145 ++++ 2 files changed, 967 insertions(+) create mode 100644 .github/skills/blazor-specialist/SKILL.md create mode 100644 .github/skills/frontend-design/SKILL.md diff --git a/.github/skills/blazor-specialist/SKILL.md b/.github/skills/blazor-specialist/SKILL.md new file mode 100644 index 0000000..e95d9b4 --- /dev/null +++ b/.github/skills/blazor-specialist/SKILL.md @@ -0,0 +1,822 @@ +--- +name: blazor-specialist +description: Blazor UI development specialist. Use for building Blazor Server or WebAssembly apps, component development, state management, or form handling. +--- + +# Blazor UI Specialist Skill + +Specialized agent for Blazor Server and Blazor WebAssembly development, component design, and state management. + +## Role + +You are a Blazor UI Specialist responsible for building interactive web interfaces using Blazor, managing component lifecycle, implementing state management, handling forms and validation, and integrating with backend APIs. + +## Expertise Areas + +- Blazor Server architecture +- Blazor WebAssembly (WASM) +- Component lifecycle and rendering +- State management (FluxState, Fluxor) +- Form validation and submission +- JavaScript interop +- SignalR integration +- Component libraries (FluentUI, MudBlazor) +- Performance optimization +- Authentication in Blazor +- Responsive design patterns + +## Responsibilities + +1. **Component Development** + - Create reusable Blazor components + - Manage component parameters and events + - Implement component lifecycle methods + - Handle component state + - Create child/parent component communication + +2. **State Management** + - Implement global state management + - Use dependency injection for services + - Manage component-level state + - Handle application-wide events + - Implement undo/redo patterns + +3. **Forms and Validation** + - Build forms with EditForm + - Implement data annotations validation + - Handle custom validation + - Display validation messages + - Submit forms to API + +4. **API Integration** + - Call backend APIs from Blazor + - Handle authentication tokens + - Display loading states + - Handle errors gracefully + - Implement optimistic UI updates + +## Load Additional Patterns + +- `.claude/patterns/api-patterns.md` + +## Critical Rules + +### Blazor Best Practices +- Use `@rendermode` appropriately (Server, WebAssembly, Auto) +- Dispose of resources in components (IDisposable) +- Avoid blocking the UI thread +- Use `StateHasChanged()` sparingly +- Minimize JavaScript interop +- Use cascading parameters for shared data +- Implement proper error boundaries + +### Component Design +- Keep components focused (single responsibility) +- Use parameters for component inputs +- Use EventCallback for component outputs +- Make components reusable +- Separate presentation from logic +- Use code-behind for complex logic + +### Performance +- Use `@key` directive for list items +- Virtualize long lists +- Lazy load routes and components +- Minimize re-renders +- Use OnInitializedAsync for async initialization +- Stream large datasets + +## Component Patterns + +### Basic Component +```razor +@* File: Components/BudgetCard.razor *@ +@namespace {ApplicationName}.UI.Components + +
+

@Budget.Name

+

@Budget.Amount.ToString("C")

+

Created: @Budget.CreatedDate.ToString("d")

+ + + +
+ +@code { + [Parameter, EditorRequired] + public BudgetResponse Budget { get; set; } = default!; + + [Parameter] + public EventCallback OnEdit { get; set; } + + [Parameter] + public EventCallback OnDelete { get; set; } + + private async Task OnEditClicked() + { + if (OnEdit.HasDelegate) + await OnEdit.InvokeAsync(Budget); + } + + private async Task OnDeleteClicked() + { + if (OnDelete.HasDelegate) + await OnDelete.InvokeAsync(Budget.BudgetId); + } +} +``` + +### Component with Code-Behind +```razor +@* File: Pages/Budgets/BudgetList.razor *@ +@page "/budgets" +@namespace {ApplicationName}.UI.Pages.Budgets +@inherits BudgetListBase + +Budgets + +
+

Budgets

+ + @if (IsLoading) + { +

Loading budgets...

+ } + else if (ErrorMessage is not null) + { +
@ErrorMessage
+ } + else if (!Budgets.Any()) + { +

No budgets found. Create your first budget!

+ } + else + { +
+ @foreach (var budget in Budgets) + { + + } +
+ } + + +
+``` + +```csharp +// File: Pages/Budgets/BudgetList.razor.cs +using Microsoft.AspNetCore.Components; +using {ApplicationName}.UI.Services; + +namespace {ApplicationName}.UI.Pages.Budgets; + +public class BudgetListBase : ComponentBase, IDisposable +{ + [Inject] + protected IBudgetService BudgetService { get; set; } = default!; + + [Inject] + protected NavigationManager Navigation { get; set; } = default!; + + [Inject] + protected ILogger Logger { get; set; } = default!; + + protected List Budgets { get; set; } = new(); + protected bool IsLoading { get; set; } + protected string? ErrorMessage { get; set; } + + protected override async Task OnInitializedAsync() + { + await LoadBudgetsAsync(); + } + + protected async Task LoadBudgetsAsync() + { + IsLoading = true; + ErrorMessage = null; + + try + { + Budgets = await BudgetService.GetBudgetsAsync(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading budgets"); + ErrorMessage = "Failed to load budgets. Please try again."; + } + finally + { + IsLoading = false; + } + } + + protected void HandleCreateBudget() + { + Navigation.NavigateTo("/budgets/create"); + } + + protected void HandleEditBudget(BudgetResponse budget) + { + Navigation.NavigateTo($"/budgets/{budget.BudgetId}/edit"); + } + + protected async Task HandleDeleteBudget(Guid budgetId) + { + try + { + await BudgetService.DeleteBudgetAsync(budgetId); + await LoadBudgetsAsync(); // Reload list + } + catch (Exception ex) + { + Logger.LogError(ex, "Error deleting budget {BudgetId}", budgetId); + ErrorMessage = "Failed to delete budget. Please try again."; + } + } + + public void Dispose() + { + // Clean up subscriptions, timers, etc. + } +} +``` + +## Form Validation Pattern + +```razor +@* File: Pages/Budgets/CreateBudget.razor *@ +@page "/budgets/create" +@namespace {ApplicationName}.UI.Pages.Budgets + +Create Budget + +
+

Create Budget

+ + + + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ + @if (ErrorMessage is not null) + { +
@ErrorMessage
+ } +
+
+ +@code { + [Inject] + private IBudgetService BudgetService { get; set; } = default!; + + [Inject] + private NavigationManager Navigation { get; set; } = default!; + + [SupplyParameterFromForm] + private CreateBudgetModel Model { get; set; } = new(); + + private bool IsSubmitting { get; set; } + private string? ErrorMessage { get; set; } + + private async Task HandleValidSubmit() + { + IsSubmitting = true; + ErrorMessage = null; + + try + { + var command = new CreateBudgetCommand( + Model.Name, + Model.Amount, + Model.StartDate); + + var result = await BudgetService.CreateBudgetAsync(command); + + Navigation.NavigateTo($"/budgets/{result.BudgetId}"); + } + catch (Exception ex) + { + ErrorMessage = "Failed to create budget. Please try again."; + } + finally + { + IsSubmitting = false; + } + } + + private void Cancel() + { + Navigation.NavigateTo("/budgets"); + } +} +``` + +```csharp +// File: Models/CreateBudgetModel.cs +using System.ComponentModel.DataAnnotations; + +public class CreateBudgetModel +{ + [Required] + [StringLength(100, MinimumLength = 3)] + public string Name { get; set; } = string.Empty; + + [Required] + [Range(0.01, 1_000_000)] + public decimal Amount { get; set; } + + [Required] + public DateTimeOffset StartDate { get; set; } = DateTimeOffset.Now; +} +``` + +## State Management (Fluxor) + +### Install Fluxor +```bash +dotnet add package Fluxor.Blazor.Web +``` + +### Define State +```csharp +// File: Store/BudgetState/BudgetState.cs +namespace {ApplicationName}.UI.Store.BudgetState; + +public record BudgetState +{ + public List Budgets { get; init; } = new(); + public bool IsLoading { get; init; } + public string? ErrorMessage { get; init; } +} +``` + +### Define Actions +```csharp +// File: Store/BudgetState/BudgetActions.cs +namespace {ApplicationName}.UI.Store.BudgetState; + +public record LoadBudgetsAction; +public record LoadBudgetsSuccessAction(List Budgets); +public record LoadBudgetsFailureAction(string ErrorMessage); +``` + +### Implement Reducer +```csharp +// File: Store/BudgetState/BudgetReducers.cs +using Fluxor; + +namespace {ApplicationName}.UI.Store.BudgetState; + +public static class BudgetReducers +{ + [ReducerMethod] + public static BudgetState ReduceLoadBudgetsAction(BudgetState state, LoadBudgetsAction action) => + state with { IsLoading = true, ErrorMessage = null }; + + [ReducerMethod] + public static BudgetState ReduceLoadBudgetsSuccessAction(BudgetState state, LoadBudgetsSuccessAction action) => + state with { IsLoading = false, Budgets = action.Budgets }; + + [ReducerMethod] + public static BudgetState ReduceLoadBudgetsFailureAction(BudgetState state, LoadBudgetsFailureAction action) => + state with { IsLoading = false, ErrorMessage = action.ErrorMessage }; +} +``` + +### Implement Effects +```csharp +// File: Store/BudgetState/BudgetEffects.cs +using Fluxor; + +namespace {ApplicationName}.UI.Store.BudgetState; + +public class BudgetEffects(IBudgetService budgetService) +{ + [EffectMethod] + public async Task HandleLoadBudgetsAction(LoadBudgetsAction action, IDispatcher dispatcher) + { + try + { + var budgets = await budgetService.GetBudgetsAsync(); + dispatcher.Dispatch(new LoadBudgetsSuccessAction(budgets)); + } + catch (Exception ex) + { + dispatcher.Dispatch(new LoadBudgetsFailureAction(ex.Message)); + } + } +} +``` + +### Register Fluxor +```csharp +// Program.cs +builder.Services.AddFluxor(options => +{ + options.ScanAssemblies(typeof(Program).Assembly); + options.UseReduxDevTools(); +}); +``` + +### Use in Component +```razor +@inherits FluxorComponent +@inject IState BudgetState +@inject IDispatcher Dispatcher + +
+ @if (BudgetState.Value.IsLoading) + { +

Loading...

+ } + else + { + @foreach (var budget in BudgetState.Value.Budgets) + { + + } + } +
+ +@code { + protected override void OnInitialized() + { + base.OnInitialized(); + Dispatcher.Dispatch(new LoadBudgetsAction()); + } +} +``` + +## API Service Pattern + +```csharp +// File: Services/BudgetService.cs +namespace {ApplicationName}.UI.Services; + +using System.Net.Http.Json; + +public interface IBudgetService +{ + Task> GetBudgetsAsync(CancellationToken cancellationToken = default); + Task GetBudgetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task CreateBudgetAsync(CreateBudgetCommand command, CancellationToken cancellationToken = default); + Task UpdateBudgetAsync(Guid id, UpdateBudgetCommand command, CancellationToken cancellationToken = default); + Task DeleteBudgetAsync(Guid id, CancellationToken cancellationToken = default); +} + +public class BudgetService( + HttpClient httpClient, + ILogger logger +) : IBudgetService +{ + public async Task> GetBudgetsAsync( + CancellationToken cancellationToken = default) + { + try + { + var budgets = await httpClient.GetFromJsonAsync>( + "/budgets", + cancellationToken); + + return budgets ?? new List(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error fetching budgets"); + throw; + } + } + + public async Task GetBudgetByIdAsync( + Guid id, + CancellationToken cancellationToken = default) + { + try + { + var budget = await httpClient.GetFromJsonAsync( + $"/budgets/{id}", + cancellationToken); + + return budget ?? throw new InvalidOperationException("Budget not found"); + } + catch (Exception ex) + { + logger.LogError(ex, "Error fetching budget {BudgetId}", id); + throw; + } + } + + public async Task CreateBudgetAsync( + CreateBudgetCommand command, + CancellationToken cancellationToken = default) + { + try + { + var response = await httpClient.PostAsJsonAsync( + "/budgets", + command, + cancellationToken); + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync( + cancellationToken: cancellationToken); + + return result ?? throw new InvalidOperationException("Failed to create budget"); + } + catch (Exception ex) + { + logger.LogError(ex, "Error creating budget"); + throw; + } + } + + public async Task UpdateBudgetAsync( + Guid id, + UpdateBudgetCommand command, + CancellationToken cancellationToken = default) + { + try + { + var response = await httpClient.PutAsJsonAsync( + $"/budgets/{id}", + command, + cancellationToken); + + response.EnsureSuccessStatusCode(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error updating budget {BudgetId}", id); + throw; + } + } + + public async Task DeleteBudgetAsync( + Guid id, + CancellationToken cancellationToken = default) + { + try + { + var response = await httpClient.DeleteAsync( + $"/budgets/{id}", + cancellationToken); + + response.EnsureSuccessStatusCode(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error deleting budget {BudgetId}", id); + throw; + } + } +} + +// Register service +builder.Services.AddScoped(); +``` + +## JavaScript Interop + +```razor +@inject IJSRuntime JS + + + + +@code { + private async Task ShowAlert() + { + await JS.InvokeVoidAsync("alert", "Hello from Blazor!"); + } + + private async Task GetLocalStorage() + { + var value = await JS.InvokeAsync("localStorage.getItem", "myKey"); + Console.WriteLine($"Value from localStorage: {value}"); + } + + private async Task SetLocalStorage() + { + await JS.InvokeVoidAsync("localStorage.setItem", "myKey", "myValue"); + } +} +``` + +```javascript +// wwwroot/js/interop.js +window.budgetApp = { + formatCurrency: function (amount) { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amount); + }, + + showConfirmDialog: function (message) { + return confirm(message); + }, + + downloadFile: function (filename, content) { + const blob = new Blob([content], { type: 'text/plain' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + } +}; +``` + +## Authentication in Blazor + +```razor +@* File: Pages/Login.razor *@ +@page "/login" +@inject NavigationManager Navigation +@inject IAuthenticationService AuthService + + + +@code { + private LoginModel loginModel = new(); + + private async Task HandleLogin() + { + var result = await AuthService.LoginAsync(loginModel.Email, loginModel.Password); + + if (result.Success) + { + Navigation.NavigateTo("/"); + } + } +} +``` + +```razor +@* File: Components/AuthorizeView.razor *@ +@using Microsoft.AspNetCore.Components.Authorization + + + +

Hello, @context.User.Identity?.Name!

+ Logout +
+ + Login + +
+``` + +## Performance Optimization + +### Virtualization +```razor +@using Microsoft.AspNetCore.Components.Web.Virtualization + + + + +``` + +### Lazy Loading +```razor +@* File: App.razor *@ + + + + + + Not found +

Sorry, there's nothing at this address.

+
+
+``` + +## Common Blazor Pitfalls + +### ❌ Avoid These Mistakes + +1. **Not Disposing Components** + - ❌ Subscribe to events without unsubscribing + - ✅ Implement IDisposable and clean up + +2. **Blocking UI Thread** + - ❌ Using `Task.Result` or `.Wait()` + - ✅ Always use `await` + +3. **Overusing StateHasChanged** + - ❌ Calling `StateHasChanged()` everywhere + - ✅ Let Blazor handle rendering automatically + +4. **Missing @key Directive** + - ❌ Rendering lists without `@key` + - ✅ Use `@key` for dynamic lists + +5. **Not Handling Errors** + - ❌ No error boundaries + - ✅ Use ErrorBoundary component + +## Blazor Checklist + +### Component Development +- [ ] Components are focused and reusable +- [ ] Parameters use `[Parameter]` attribute +- [ ] EventCallbacks for component events +- [ ] Code-behind for complex logic +- [ ] IDisposable implemented where needed + +### Forms & Validation +- [ ] EditForm used for forms +- [ ] Data annotations validation +- [ ] ValidationSummary displayed +- [ ] Submit button disabled during submission +- [ ] Error messages displayed + +### State Management +- [ ] Global state managed (Fluxor/FluxState) +- [ ] Component state localized +- [ ] Services injected via DI +- [ ] State changes trigger re-renders + +### API Integration +- [ ] HttpClient configured +- [ ] Loading states displayed +- [ ] Error handling implemented +- [ ] Authentication tokens included +- [ ] Retry logic for transient failures + +### Performance +- [ ] Virtualization for long lists +- [ ] Lazy loading for routes +- [ ] @key directive on lists +- [ ] Minimal JavaScript interop +- [ ] Component disposal implemented + +## Checklist Before Completion + +- [ ] All components render correctly +- [ ] Forms validate and submit +- [ ] API calls successful +- [ ] Loading states displayed +- [ ] Error handling functional +- [ ] Authentication working +- [ ] State management functional +- [ ] Performance optimized +- [ ] Responsive design implemented +- [ ] Documentation complete diff --git a/.github/skills/frontend-design/SKILL.md b/.github/skills/frontend-design/SKILL.md new file mode 100644 index 0000000..7f0b0c3 --- /dev/null +++ b/.github/skills/frontend-design/SKILL.md @@ -0,0 +1,145 @@ +--- +name: frontend-design +description: Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality. +origin: ECC +--- + +# Frontend Design + +Use this when the task is not just "make it work" but "make it look designed." + +This skill is for product pages, dashboards, app shells, components, or visual systems that need a clear point of view instead of generic AI-looking UI. + +## When To Use + +- building a landing page, dashboard, or app surface from scratch +- upgrading a bland interface into something intentional and memorable +- translating a product concept into a concrete visual direction +- implementing a frontend where typography, composition, and motion matter + +## Core Principle + +Pick a direction and commit to it. + +Safe-average UI is usually worse than a strong, coherent aesthetic with a few bold choices. + +## Design Workflow + +### 1. Frame the interface first + +Before coding, settle: + +- purpose +- audience +- emotional tone +- visual direction +- one thing the user should remember + +Possible directions: + +- brutally minimal +- editorial +- industrial +- luxury +- playful +- geometric +- retro-futurist +- soft and organic +- maximalist + +Do not mix directions casually. Choose one and execute it cleanly. + +### 2. Build the visual system + +Define: + +- type hierarchy +- color variables +- spacing rhythm +- layout logic +- motion rules +- surface / border / shadow treatment + +Use CSS variables or the project's token system so the interface stays coherent as it grows. + +### 3. Compose with intention + +Prefer: + +- asymmetry when it sharpens hierarchy +- overlap when it creates depth +- strong whitespace when it clarifies focus +- dense layouts only when the product benefits from density + +Avoid defaulting to a symmetrical card grid unless it is clearly the right fit. + +### 4. Make motion meaningful + +Use animation to: + +- reveal hierarchy +- stage information +- reinforce user action +- create one or two memorable moments + +Do not scatter generic micro-interactions everywhere. One well-directed load sequence is usually stronger than twenty random hover effects. + +## Strong Defaults + +### Typography + +- pick fonts with character +- pair a distinctive display face with a readable body face when appropriate +- avoid generic defaults when the page is design-led + +### Color + +- commit to a clear palette +- one dominant field with selective accents usually works better than evenly weighted rainbow palettes +- avoid cliché purple-gradient-on-white unless the product genuinely calls for it + +### Background + +Use atmosphere: + +- gradients +- meshes +- textures +- subtle noise +- patterns +- layered transparency + +Flat empty backgrounds are rarely the best answer for a product-facing page. + +### Layout + +- break the grid when the composition benefits from it +- use diagonals, offsets, and grouping intentionally +- keep reading flow obvious even when the layout is unconventional + +## Anti-Patterns + +Never default to: + +- interchangeable SaaS hero sections +- generic card piles with no hierarchy +- random accent colors without a system +- placeholder-feeling typography +- motion that exists only because animation was easy to add + +## Execution Rules + +- preserve the established design system when working inside an existing product +- match technical complexity to the visual idea +- keep accessibility and responsiveness intact +- frontends should feel deliberate on desktop and mobile + +## Quality Gate + +Before delivering: + +- the interface has a clear visual point of view +- typography and spacing feel intentional +- color and motion support the product instead of decorating it randomly +- the result does not read like generic AI UI +- the implementation is production-grade, not just visually interesting