diff --git a/.claude/skills/api-sync/SKILL.md b/.claude/skills/api-sync/SKILL.md index 637ed7d72..02d21a3cf 100644 --- a/.claude/skills/api-sync/SKILL.md +++ b/.claude/skills/api-sync/SKILL.md @@ -1,371 +1,362 @@ ---- -name: api-sync -description: This skill should be used when regenerating Swagger/OpenAPI TypeScript API clients with breaking change detection. Handles generation of all 10 API clients (or specific ones), pre-generation impact analysis, Unicode cleanup, TypeScript validation, and affected test execution. Use when user requests "API sync", "regenerate swagger", "check breaking changes", or indicates backend API changes. ---- - -# API Sync Manager - -## Overview - -Automate the complete lifecycle of TypeScript API client regeneration from Swagger/OpenAPI specifications. Provides pre-generation breaking change detection, automatic post-processing, impact analysis, validation, and migration recommendations for all 10 API clients in the ISA-Frontend monorepo. - -## When to Use This Skill - -Invoke when user requests: -- API client regeneration or sync -- "Check breaking changes" before API update -- Backend API changes need frontend updates -- Impact assessment of API changes -- "Regenerate swagger" or "update API clients" - -## Available APIs - -availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api - -## Unified Sync Workflow - -### Step 1: Pre-Generation Check - -```bash -# Check uncommitted changes -git status generated/swagger/ - -# Verify no manual edits will be lost -git diff generated/swagger/ -``` - -If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first. - -### Step 2: Pre-Generation Breaking Change Detection - -Generate to temporary location to compare without affecting working directory: - -```bash -# Backup current state -cp -r generated/swagger/[api-name] /tmp/[api-name].backup - -# Generate to temp location for comparison -npm run generate:swagger:[api-name] -``` - -**Compare Models and Services:** - -```bash -diff -u /tmp/[api-name].backup/models.ts generated/swagger/[api-name]/models.ts -diff -u /tmp/[api-name].backup/services.ts generated/swagger/[api-name]/services.ts -``` - -**Categorize Changes:** - -**πŸ”΄ Breaking (Critical):** -- Removed properties from response models -- Changed property types (string β†’ number, object β†’ array) -- Removed endpoints -- Optional β†’ required fields in request models -- Removed enum values -- Changed endpoint paths or HTTP methods - -**⚠️ Warnings (Review Required):** -- Property renamed (old removed + new added) -- Changed default values -- Changed validation rules (min/max length, pattern) -- Added required request fields -- Changed parameter locations (query β†’ body) - -**βœ… Compatible (Safe):** -- Added properties to response models -- New endpoints -- Added optional parameters -- New enum values -- Required β†’ optional fields - -### Step 3: Impact Analysis - -For each breaking or warning change, analyze codebase impact: - -```bash -# Find all imports from affected API -grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts" - -# Find usages of specific removed/changed items -grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts" -``` - -**Document:** -- Affected files (with line numbers) -- Services impacted -- Components/stores using affected services -- Estimated refactoring effort (hours) - -### Step 4: Generate Migration Strategy - -Based on breaking change severity: - -**High Impact (>5 breaking changes or critical endpoints):** -1. Create dedicated migration branch from develop -2. Document all changes in migration guide -3. Update data-access services incrementally -4. Update affected stores and components -5. Comprehensive test coverage -6. Coordinate deployment with backend team - -**Medium Impact (2-5 breaking changes):** -1. Fix TypeScript compilation errors -2. Update affected data-access services -3. Update tests -4. Run affected test suite -5. Deploy with monitoring - -**Low Impact (1 breaking change, minor impact):** -1. Apply minimal updates -2. Verify tests pass -3. Deploy normally - -### Step 5: Execute Generation - -If user approves proceeding after impact analysis: - -```bash -# All APIs -npm run generate:swagger - -# Specific API (if api-name provided) -npm run generate:swagger:[api-name] -``` - -### Step 6: Verify Unicode Cleanup - -Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify: - -```bash -# Scan for remaining Unicode issues -grep -r "\\\\u00" generated/swagger/ || echo "βœ… No Unicode issues" -``` - -If issues remain, run manually: - -```bash -node tools/fix-files.js -``` - -### Step 7: TypeScript Validation - -```bash -# Full TypeScript compilation check -npx tsc --noEmit - -# If errors, show affected files -npx tsc --noEmit | head -20 -``` - -Document compilation errors: -- Missing properties -- Type mismatches -- Incompatible signatures - -### Step 8: Run Affected Tests - -```bash -# Run tests for affected projects -npx nx affected:test --skip-nx-cache --base=HEAD~1 - -# Lint affected projects -npx nx affected:lint --base=HEAD~1 -``` - -Monitor test failures and categorize: -- Mock data mismatches (update fixtures) -- Type assertion failures (update test types) -- Integration test failures (API contract changes) - -### Step 9: Generate Comprehensive Report - -``` -API Sync Manager Report -======================= - -API: [api-name | all] -Sync Date: [timestamp] -Generation: βœ… Success / ❌ Failed - -πŸ“Š Change Summary ------------------ -Breaking Changes: XX -Warnings: XX -Compatible Changes: XX -Files Modified: XX - -πŸ”΄ Breaking Changes -------------------- -1. [API Name] - Removed Property: OrderResponse.deliveryDate - Impact: Medium (2 files affected) - Files: - - libs/oms/data-access/src/lib/services/order.service.ts:45 - - libs/oms/feature/order-detail/src/lib/component.ts:78 - Fix Strategy: Remove references or use alternativeDate field - Estimated Effort: 30 minutes - -2. [API Name] - Type Changed: ProductResponse.price (string β†’ number) - Impact: High (1 file + cascading changes) - Files: - - libs/catalogue/data-access/src/lib/services/product.service.ts:32 - Fix Strategy: Remove string parsing, update price calculations - Estimated Effort: 1 hour - -⚠️ Warnings (Review Required) ------------------------------- -1. [API Name] - Possible Rename: CustomerResponse.customerName β†’ fullName - Action: Verify with backend team if intentional - Migration: Update references if confirmed rename - -2. [API Name] - New Required Field: CreateOrderRequest.taxId - Action: Update order creation flows to provide taxId - Impact: 3 order creation forms - -βœ… Compatible Changes ---------------------- -1. [API Name] - Added Property: OrderResponse.estimatedDelivery -2. [API Name] - New Endpoint: GET /api/v2/orders/bulk -3. [API Name] - Added Optional Parameter: includeArchived to GET /orders - -πŸ“Š Validation Results ---------------------- -TypeScript Compilation: βœ… Pass / ❌ XX errors -Unicode Cleanup: βœ… Complete -Tests: XX/XX passing (YY affected) -Lint: βœ… Pass / ⚠️ XX warnings - -❌ Test Failures (if any) -------------------------- -1. order.service.spec.ts - Mock response missing deliveryDate - Fix: Update mock fixture -2. product.component.spec.ts - Price type assertion failed - Fix: Change expect(price).toBe("10.99") β†’ expect(price).toBe(10.99) - -πŸ’‘ Migration Strategy ---------------------- -Approach: [High/Medium/Low Impact] -Estimated Total Effort: [hours] - -Steps: -1. [Fix compilation errors in data-access services] -2. [Update test mocks and fixtures] -3. [Update components using affected properties] -4. [Run full test suite] -5. [Deploy with backend coordination] - -🎯 Recommendation ------------------ -[One of the following:] -βœ… Safe to proceed - Only compatible changes detected -⚠️ Proceed with caution - Fix breaking changes before deployment -πŸ”΄ Coordinate with backend - High impact changes require migration plan -πŸ›‘ Block regeneration - Critical breaking changes, backend rollback needed - -πŸ“‹ Next Steps -------------- -[Specific actions user should take] -- [ ] Fix compilation errors in [files] -- [ ] Update test mocks in [files] -- [ ] Coordinate deployment timing with backend team -- [ ] Monitor [specific endpoints] after deployment -``` - -### Step 10: Cleanup - -```bash -# Remove temporary backup -rm -rf /tmp/[api-name].backup - -# Or restore if generation needs to be reverted -# cp -r /tmp/[api-name].backup generated/swagger/[api-name] -``` - -## Error Handling - -**Generation Fails:** -- Check OpenAPI spec URLs in package.json scripts -- Verify backend API is accessible -- Check for malformed OpenAPI specification - -**Unicode Cleanup Fails:** -- Run `node tools/fix-files.js` manually -- Check for new Unicode patterns not covered by script - -**TypeScript Compilation Errors:** -- Review breaking changes section in report -- Update affected data-access services first -- Fix cascading type errors in consumers - -**Test Failures:** -- Update mock data to match new API contracts -- Fix type assertions in tests -- Update integration test expectations - -**Diff Detection Issues:** -- Ensure clean git state before running -- Check file permissions on generated/ directory -- Verify backup location is writable - -## Advanced Usage - -**Compare Specific Endpoints Only:** - -```bash -# Extract and compare specific interface -grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts -grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts -``` - -**Bulk API Sync with Change Detection:** - -Run pre-generation detection for all APIs before regenerating: - -```bash -for api in availability-api cat-search-api checkout-api crm-api eis-api inventory-api isa-api oms-api print-api wws-api; do - echo "Checking $api..." - # Run detection workflow for each -done -``` - -Generate consolidated report across all APIs before committing. - -**Rollback Procedure:** - -```bash -# If sync causes critical issues -git checkout generated/swagger/[api-name] -git clean -fd generated/swagger/[api-name] - -# Or restore from backup -cp -r generated/swagger.backup.[timestamp]/* generated/swagger/ -``` - -## Integration with Git Workflow - -**Recommended Commit Message:** - -``` -feat(api): sync [api-name] API client [TASK-####] - -Breaking changes: -- Removed OrderResponse.deliveryDate (use estimatedDelivery) -- Changed ProductResponse.price type (string β†’ number) - -Compatible changes: -- Added OrderResponse.estimatedDelivery -- New endpoint GET /api/v2/orders/bulk -``` - -**Branch Strategy:** - -- Low impact: Commit to feature branch directly -- Medium impact: Create `sync/[api-name]-[date]` branch -- High impact: Create `migration/[api-name]-[version]` branch with migration guide - -## References - -- CLAUDE.md API Integration section -- package.json swagger generation scripts -- tools/fix-files.js for Unicode cleanup logic -- Semantic Versioning: https://semver.org +--- +name: api-sync +description: This skill should be used when regenerating Swagger/OpenAPI TypeScript API clients with breaking change detection. Handles generation of all 10 API clients (or specific ones), pre-generation impact analysis, Unicode cleanup, TypeScript validation, and affected test execution. Use when user requests "API sync", "regenerate swagger", "check breaking changes", or indicates backend API changes. +--- + +# API Sync Manager + +## Overview + +Automate the complete lifecycle of TypeScript API client regeneration from Swagger/OpenAPI specifications. Provides pre-generation breaking change detection, automatic post-processing, impact analysis, validation, and migration recommendations for all 10 API clients in the ISA-Frontend monorepo. + +## Available APIs + +availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api + +## Unified Sync Workflow + +### Step 1: Pre-Generation Check + +```bash +# Check uncommitted changes +git status generated/swagger/ + +# Verify no manual edits will be lost +git diff generated/swagger/ +``` + +If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first. + +### Step 2: Pre-Generation Breaking Change Detection + +Generate to temporary location to compare without affecting working directory: + +```bash +# Backup current state +cp -r generated/swagger/[api-name] /tmp/[api-name].backup + +# Generate to temp location for comparison +npm run generate:swagger:[api-name] +``` + +**Compare Models and Services:** + +```bash +diff -u /tmp/[api-name].backup/models.ts generated/swagger/[api-name]/models.ts +diff -u /tmp/[api-name].backup/services.ts generated/swagger/[api-name]/services.ts +``` + +**Categorize Changes:** + +**πŸ”΄ Breaking (Critical):** +- Removed properties from response models +- Changed property types (string β†’ number, object β†’ array) +- Removed endpoints +- Optional β†’ required fields in request models +- Removed enum values +- Changed endpoint paths or HTTP methods + +**⚠️ Warnings (Review Required):** +- Property renamed (old removed + new added) +- Changed default values +- Changed validation rules (min/max length, pattern) +- Added required request fields +- Changed parameter locations (query β†’ body) + +**βœ… Compatible (Safe):** +- Added properties to response models +- New endpoints +- Added optional parameters +- New enum values +- Required β†’ optional fields + +### Step 3: Impact Analysis + +For each breaking or warning change, analyze codebase impact: + +```bash +# Find all imports from affected API +grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts" + +# Find usages of specific removed/changed items +grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts" +``` + +**Document:** +- Affected files (with line numbers) +- Services impacted +- Components/stores using affected services +- Estimated refactoring effort (hours) + +### Step 4: Generate Migration Strategy + +Based on breaking change severity: + +**High Impact (>5 breaking changes or critical endpoints):** +1. Create dedicated migration branch from develop +2. Document all changes in migration guide +3. Update data-access services incrementally +4. Update affected stores and components +5. Comprehensive test coverage +6. Coordinate deployment with backend team + +**Medium Impact (2-5 breaking changes):** +1. Fix TypeScript compilation errors +2. Update affected data-access services +3. Update tests +4. Run affected test suite +5. Deploy with monitoring + +**Low Impact (1 breaking change, minor impact):** +1. Apply minimal updates +2. Verify tests pass +3. Deploy normally + +### Step 5: Execute Generation + +If user approves proceeding after impact analysis: + +```bash +# All APIs +npm run generate:swagger + +# Specific API (if api-name provided) +npm run generate:swagger:[api-name] +``` + +### Step 6: Verify Unicode Cleanup + +Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify: + +```bash +# Scan for remaining Unicode issues +grep -r "\\\\u00" generated/swagger/ || echo "βœ… No Unicode issues" +``` + +If issues remain, run manually: + +```bash +node tools/fix-files.js +``` + +### Step 7: TypeScript Validation + +```bash +# Full TypeScript compilation check +npx tsc --noEmit + +# If errors, show affected files +npx tsc --noEmit | head -20 +``` + +Document compilation errors: +- Missing properties +- Type mismatches +- Incompatible signatures + +### Step 8: Run Affected Tests + +```bash +# Run tests for affected projects +npx nx affected:test --skip-nx-cache --base=HEAD~1 + +# Lint affected projects +npx nx affected:lint --base=HEAD~1 +``` + +Monitor test failures and categorize: +- Mock data mismatches (update fixtures) +- Type assertion failures (update test types) +- Integration test failures (API contract changes) + +### Step 9: Generate Comprehensive Report + +``` +API Sync Manager Report +======================= + +API: [api-name | all] +Sync Date: [timestamp] +Generation: βœ… Success / ❌ Failed + +πŸ“Š Change Summary +----------------- +Breaking Changes: XX +Warnings: XX +Compatible Changes: XX +Files Modified: XX + +πŸ”΄ Breaking Changes +------------------- +1. [API Name] - Removed Property: OrderResponse.deliveryDate + Impact: Medium (2 files affected) + Files: + - libs/oms/data-access/src/lib/services/order.service.ts:45 + - libs/oms/feature/order-detail/src/lib/component.ts:78 + Fix Strategy: Remove references or use alternativeDate field + Estimated Effort: 30 minutes + +2. [API Name] - Type Changed: ProductResponse.price (string β†’ number) + Impact: High (1 file + cascading changes) + Files: + - libs/catalogue/data-access/src/lib/services/product.service.ts:32 + Fix Strategy: Remove string parsing, update price calculations + Estimated Effort: 1 hour + +⚠️ Warnings (Review Required) +------------------------------ +1. [API Name] - Possible Rename: CustomerResponse.customerName β†’ fullName + Action: Verify with backend team if intentional + Migration: Update references if confirmed rename + +2. [API Name] - New Required Field: CreateOrderRequest.taxId + Action: Update order creation flows to provide taxId + Impact: 3 order creation forms + +βœ… Compatible Changes +--------------------- +1. [API Name] - Added Property: OrderResponse.estimatedDelivery +2. [API Name] - New Endpoint: GET /api/v2/orders/bulk +3. [API Name] - Added Optional Parameter: includeArchived to GET /orders + +πŸ“Š Validation Results +--------------------- +TypeScript Compilation: βœ… Pass / ❌ XX errors +Unicode Cleanup: βœ… Complete +Tests: XX/XX passing (YY affected) +Lint: βœ… Pass / ⚠️ XX warnings + +❌ Test Failures (if any) +------------------------- +1. order.service.spec.ts - Mock response missing deliveryDate + Fix: Update mock fixture +2. product.component.spec.ts - Price type assertion failed + Fix: Change expect(price).toBe("10.99") β†’ expect(price).toBe(10.99) + +πŸ’‘ Migration Strategy +--------------------- +Approach: [High/Medium/Low Impact] +Estimated Total Effort: [hours] + +Steps: +1. [Fix compilation errors in data-access services] +2. [Update test mocks and fixtures] +3. [Update components using affected properties] +4. [Run full test suite] +5. [Deploy with backend coordination] + +🎯 Recommendation +----------------- +[One of the following:] +βœ… Safe to proceed - Only compatible changes detected +⚠️ Proceed with caution - Fix breaking changes before deployment +πŸ”΄ Coordinate with backend - High impact changes require migration plan +πŸ›‘ Block regeneration - Critical breaking changes, backend rollback needed + +πŸ“‹ Next Steps +------------- +[Specific actions user should take] +- [ ] Fix compilation errors in [files] +- [ ] Update test mocks in [files] +- [ ] Coordinate deployment timing with backend team +- [ ] Monitor [specific endpoints] after deployment +``` + +### Step 10: Cleanup + +```bash +# Remove temporary backup +rm -rf /tmp/[api-name].backup + +# Or restore if generation needs to be reverted +# cp -r /tmp/[api-name].backup generated/swagger/[api-name] +``` + +## Error Handling + +**Generation Fails:** +- Check OpenAPI spec URLs in package.json scripts +- Verify backend API is accessible +- Check for malformed OpenAPI specification + +**Unicode Cleanup Fails:** +- Run `node tools/fix-files.js` manually +- Check for new Unicode patterns not covered by script + +**TypeScript Compilation Errors:** +- Review breaking changes section in report +- Update affected data-access services first +- Fix cascading type errors in consumers + +**Test Failures:** +- Update mock data to match new API contracts +- Fix type assertions in tests +- Update integration test expectations + +**Diff Detection Issues:** +- Ensure clean git state before running +- Check file permissions on generated/ directory +- Verify backup location is writable + +## Advanced Usage + +**Compare Specific Endpoints Only:** + +```bash +# Extract and compare specific interface +grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts +grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts +``` + +**Bulk API Sync with Change Detection:** + +Run pre-generation detection for all APIs before regenerating: + +```bash +for api in availability-api cat-search-api checkout-api crm-api eis-api inventory-api isa-api oms-api print-api wws-api; do + echo "Checking $api..." + # Run detection workflow for each +done +``` + +Generate consolidated report across all APIs before committing. + +**Rollback Procedure:** + +```bash +# If sync causes critical issues +git checkout generated/swagger/[api-name] +git clean -fd generated/swagger/[api-name] + +# Or restore from backup +cp -r generated/swagger.backup.[timestamp]/* generated/swagger/ +``` + +## Integration with Git Workflow + +**Recommended Commit Message:** + +``` +feat(api): sync [api-name] API client [TASK-####] + +Breaking changes: +- Removed OrderResponse.deliveryDate (use estimatedDelivery) +- Changed ProductResponse.price type (string β†’ number) + +Compatible changes: +- Added OrderResponse.estimatedDelivery +- New endpoint GET /api/v2/orders/bulk +``` + +**Branch Strategy:** + +- Low impact: Commit to feature branch directly +- Medium impact: Create `sync/[api-name]-[date]` branch +- High impact: Create `migration/[api-name]-[version]` branch with migration guide + +## References + +- CLAUDE.md API Integration section +- package.json swagger generation scripts +- tools/fix-files.js for Unicode cleanup logic +- Semantic Versioning: https://semver.org diff --git a/.claude/skills/arch-docs/SKILL.md b/.claude/skills/arch-docs/SKILL.md index e8aa546f6..28321ffe9 100644 --- a/.claude/skills/arch-docs/SKILL.md +++ b/.claude/skills/arch-docs/SKILL.md @@ -1,171 +1,177 @@ ---- -name: arch-docs -description: Generate architecture documentation (C4, Arc42, ADRs, PlantUML). Auto-invoke when user mentions "architecture docs", "C4 model", "ADR", "document architecture", "system design", or "create architecture diagram". ---- - -# Architecture Documentation Skill - -Generate comprehensive architecture documentation using modern frameworks and best practices. - -## When to Use - -- Creating or updating architecture documentation -- Generating C4 model diagrams (Context, Container, Component, Code) -- Writing Architecture Decision Records (ADRs) -- Documenting system design and component relationships -- Creating PlantUML or Mermaid diagrams - -## Available Frameworks - -### C4 Model -Best for: Visualizing software architecture at different abstraction levels - -Levels: -1. **Context** - System landscape and external actors -2. **Container** - High-level technology choices (apps, databases, etc.) -3. **Component** - Internal structure of containers -4. **Code** - Class/module level detail (optional) - -See: `@references/c4-model.md` for patterns and examples - -### Arc42 Template -Best for: Comprehensive architecture documentation - -Sections: -1. Introduction and Goals -2. Constraints -3. Context and Scope -4. Solution Strategy -5. Building Block View -6. Runtime View -7. Deployment View -8. Cross-cutting Concepts -9. Architecture Decisions -10. Quality Requirements -11. Risks and Technical Debt -12. Glossary - -See: `@references/arc42.md` for template structure - -### Architecture Decision Records (ADRs) -Best for: Documenting individual architectural decisions - -See: `@references/adr-template.md` for format and examples - -## Workflow - -### 1. Discovery Phase -```bash -# Find existing architecture files -find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd" - -# Identify service boundaries -cat nx.json docker-compose.yml - -# Check for existing ADRs -ls -la docs/adr/ docs/decisions/ -``` - -### 2. Analysis Phase -- Analyze codebase structure (`libs/`, `apps/`) -- Identify dependencies from `tsconfig.base.json` paths -- Review service boundaries from `project.json` tags -- Map data flow from API definitions - -### 3. Documentation Phase -Based on the request, create appropriate documentation: - -**For C4 diagrams:** -``` -docs/architecture/ -β”œβ”€β”€ c4-context.puml -β”œβ”€β”€ c4-container.puml -└── c4-component-[name].puml -``` - -**For ADRs:** -``` -docs/adr/ -β”œβ”€β”€ 0001-record-architecture-decisions.md -β”œβ”€β”€ 0002-[decision-title].md -└── template.md -``` - -**For Arc42:** -``` -docs/architecture/ -└── arc42/ - β”œβ”€β”€ 01-introduction.md - β”œβ”€β”€ 02-constraints.md - └── ... -``` - -## ISA-Frontend Specific Context - -### Monorepo Structure -- **apps/**: Angular applications -- **libs/**: Shared libraries organized by domain - - `libs/[domain]/feature/` - Feature modules - - `libs/[domain]/data-access/` - State management - - `libs/[domain]/ui/` - Presentational components - - `libs/[domain]/util/` - Utilities - -### Key Architectural Patterns -- **Nx Monorepo** with strict module boundaries -- **NgRx Signal Store** for state management -- **Standalone Components** (Angular 20+) -- **Domain-Driven Design** library organization - -### Documentation Locations -- ADRs: `docs/adr/` -- Architecture diagrams: `docs/architecture/` -- API documentation: Generated from Swagger/OpenAPI - -## Output Standards - -### PlantUML Format -```plantuml -@startuml C4_Context -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml - -Person(user, "User", "System user") -System(system, "ISA System", "Main application") -System_Ext(external, "External API", "Third-party service") - -Rel(user, system, "Uses") -Rel(system, external, "Calls") -@enduml -``` - -### Mermaid Format -```mermaid -graph TD - A[User] --> B[ISA App] - B --> C[API Gateway] - C --> D[Backend Services] -``` - -### ADR Format -```markdown -# ADR-XXXX: [Title] - -## Status -[Proposed | Accepted | Deprecated | Superseded] - -## Context -[What is the issue?] - -## Decision -[What was decided?] - -## Consequences -[What are the results?] -``` - -## Best Practices - -1. **Start with Context** - Always begin with C4 Level 1 (System Context) -2. **Use Consistent Notation** - Stick to one diagramming tool/format -3. **Keep ADRs Atomic** - One decision per ADR -4. **Version Control** - Commit documentation with code changes -5. **Review Regularly** - Architecture docs decay; schedule reviews +--- +name: arch-docs +description: Generate architecture documentation (C4, Arc42, ADRs, PlantUML). Auto-invoke when user mentions "architecture docs", "C4 model", "ADR", "document architecture", "system design", or "create architecture diagram". +--- + +# Architecture Documentation + +Generate comprehensive architecture documentation using modern frameworks and best practices. + +## Available Frameworks + +### C4 Model +Visualize software architecture at different abstraction levels. + +**Levels:** +1. **Context** - System landscape and external actors +2. **Container** - High-level technology choices (apps, databases, etc.) +3. **Component** - Internal structure of containers +4. **Code** - Class/module level detail (optional) + +**When to use:** Visualizing system architecture, creating diagrams for stakeholders at different technical levels. + +See `references/c4-model.md` for detailed patterns and examples. + +### Arc42 Template +Comprehensive architecture documentation covering all aspects. + +**Sections:** +1. Introduction and Goals +2. Constraints +3. Context and Scope +4. Solution Strategy +5. Building Block View +6. Runtime View +7. Deployment View +8. Cross-cutting Concepts +9. Architecture Decisions +10. Quality Requirements +11. Risks and Technical Debt +12. Glossary + +**When to use:** Creating complete architecture documentation, documenting system-wide concerns, establishing quality goals. + +See `references/arc42.md` for complete template structure. + +### Architecture Decision Records (ADRs) +Document individual architectural decisions with context and consequences. + +**When to use:** Recording significant decisions, documenting trade-offs, tracking architectural evolution. + +See `references/adr-template.md` for format and examples. + +## Workflow + +### 1. Discovery Phase +Understand the system structure: + +```bash +# Find existing architecture files +find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd" + +# Identify service boundaries +cat nx.json docker-compose.yml + +# Check for existing ADRs +ls -la docs/adr/ docs/decisions/ +``` + +### 2. Analysis Phase +- Analyze codebase structure (`libs/`, `apps/`) +- Identify dependencies from `tsconfig.base.json` paths +- Review service boundaries from `project.json` tags +- Map data flow from API definitions + +### 3. Documentation Phase +Choose appropriate output format based on request: + +**For C4 diagrams:** +``` +docs/architecture/ +β”œβ”€β”€ c4-context.puml +β”œβ”€β”€ c4-container.puml +└── c4-component-[name].puml +``` + +**For ADRs:** +``` +docs/adr/ +β”œβ”€β”€ 0001-record-architecture-decisions.md +β”œβ”€β”€ 0002-[decision-title].md +└── template.md +``` + +**For Arc42:** +``` +docs/architecture/ +└── arc42/ + β”œβ”€β”€ 01-introduction.md + β”œβ”€β”€ 02-constraints.md + └── ... +``` + +## ISA-Frontend Context + +### Monorepo Structure +- **apps/**: Angular applications +- **libs/**: Shared libraries organized by domain + - `libs/[domain]/feature/` - Feature modules + - `libs/[domain]/data-access/` - State management + - `libs/[domain]/ui/` - Presentational components + - `libs/[domain]/util/` - Utilities + +### Key Architectural Patterns +- **Nx Monorepo** with strict module boundaries +- **NgRx Signal Store** for state management +- **Standalone Components** (Angular 20+) +- **Domain-Driven Design** library organization + +### Documentation Locations +- ADRs: `docs/adr/` +- Architecture diagrams: `docs/architecture/` +- API documentation: Generated from Swagger/OpenAPI + +## Output Standards + +### PlantUML Format +```plantuml +@startuml C4_Context +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml + +Person(user, "User", "System user") +System(system, "ISA System", "Main application") +System_Ext(external, "External API", "Third-party service") + +Rel(user, system, "Uses") +Rel(system, external, "Calls") +@enduml +``` + +### Mermaid Format +```mermaid +graph TD + A[User] --> B[ISA App] + B --> C[API Gateway] + C --> D[Backend Services] +``` + +### ADR Format +```markdown +# ADR-XXXX: [Title] + +## Status +[Proposed | Accepted | Deprecated | Superseded] + +## Context +[What is the issue?] + +## Decision +[What was decided?] + +## Consequences +[What are the results?] +``` + +## Best Practices + +1. **Start with Context** - Always begin with C4 Level 1 (System Context) +2. **Use Consistent Notation** - Stick to one diagramming tool/format +3. **Keep ADRs Atomic** - One decision per ADR +4. **Version Control** - Commit documentation with code changes +5. **Review Regularly** - Architecture docs decay; schedule reviews + +## References + +- `references/c4-model.md` - C4 model patterns, templates, and ISA-Frontend domain structure +- `references/arc42.md` - Complete Arc42 template with all 12 sections +- `references/adr-template.md` - ADR format with examples and naming conventions diff --git a/.claude/skills/architecture-validator/SKILL.md b/.claude/skills/architecture-validator/SKILL.md index b2f2406a9..e66e8bcb7 100644 --- a/.claude/skills/architecture-validator/SKILL.md +++ b/.claude/skills/architecture-validator/SKILL.md @@ -9,16 +9,6 @@ description: This skill should be used when validating architecture compliance, Validate and enforce architectural boundaries in the ISA-Frontend monorepo. Detects import boundary violations, circular dependencies, layer violations, and cross-domain dependencies. Provides automated fix strategies using graph analysis, dependency injection, interface extraction, and shared code refactoring. -## When to Use This Skill - -Invoke when user wants to: -- Validate import boundaries or architectural rules -- Detect circular dependencies or dependency cycles -- Find layer violations (Featureβ†’Feature, Data Accessβ†’Feature) -- Check cross-domain dependencies -- Resolve build failures with circular import warnings -- Mentioned "check architecture", "validate boundaries", "circular dependencies", or "dependency cycles" - ## Architectural Rules **Allowed Dependencies:** diff --git a/.claude/skills/css-animations/SKILL.md b/.claude/skills/css-animations/SKILL.md index ffa46fa3b..a7ba7ed7d 100644 --- a/.claude/skills/css-animations/SKILL.md +++ b/.claude/skills/css-animations/SKILL.md @@ -9,17 +9,6 @@ description: This skill should be used when writing or reviewing CSS animations Implement native CSS @keyframes animations for Angular applications, replacing @angular/animations with GPU-accelerated, zero-bundle-size alternatives. This skill provides comprehensive guidance on creating performant entrance/exit animations, staggered effects, and proper timing configurations. -## When to Use This Skill - -Apply this skill when: -- **Writing Angular components** with entrance/exit animations -- **Converting @angular/animations** to native CSS @keyframes -- **Implementing animate.enter/animate.leave** in Angular 20+ templates -- **Creating staggered animations** for lists or collections -- **Debugging animation issues** (snap-back, wrong starting positions, choppy playback) -- **Optimizing animation performance** for GPU acceleration -- **Reviewing animation code** for accessibility and best practices - ## Quick Start ### Basic Animation Setup @@ -311,41 +300,6 @@ element.addEventListener('animationend', (e) => { }); ``` -## Resources - -### references/keyframes-guide.md - -Comprehensive deep-dive covering: -- Complete @keyframes syntax reference -- Detailed timing functions and cubic-bezier curves -- Advanced techniques (direction, play-state, @starting-style) -- Performance optimization strategies -- Extensive common patterns library -- Debugging techniques and troubleshooting - -**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details. - -### assets/animations.css - -Ready-to-use CSS file with common animation patterns: -- Fade animations (in/out) -- Slide animations (up/down/left/right) -- Scale animations (in/out) -- Utility animations (spin, shimmer, shake, breathe, attention-pulse) -- Toast/notification animations -- Accessibility (@media prefers-reduced-motion) - -**Usage:** Copy this file to project and import in component styles or global styles: - -```css -@import 'path/to/animations.css'; -``` - -Then use classes directly: -```html -
-``` - ## Migration from @angular/animations ### Before (Angular Animations) @@ -390,3 +344,38 @@ import { trigger, state, style, transition, animate } from '@angular/animations' - GPU hardware acceleration - Standard CSS (transferable skills) - Better performance + +## Resources + +### references/keyframes-guide.md + +Comprehensive deep-dive covering: +- Complete @keyframes syntax reference +- Detailed timing functions and cubic-bezier curves +- Advanced techniques (direction, play-state, @starting-style) +- Performance optimization strategies +- Extensive common patterns library +- Debugging techniques and troubleshooting + +**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details. + +### assets/animations.css + +Ready-to-use CSS file with common animation patterns: +- Fade animations (in/out) +- Slide animations (up/down/left/right) +- Scale animations (in/out) +- Utility animations (spin, shimmer, shake, breathe, attention-pulse) +- Toast/notification animations +- Accessibility (@media prefers-reduced-motion) + +**Usage:** Copy this file to project and import in component styles or global styles: + +```css +@import 'path/to/animations.css'; +``` + +Then use classes directly: +```html +
+``` diff --git a/.claude/skills/git-workflow/SKILL.md b/.claude/skills/git-workflow/SKILL.md index af1c2b985..7fd007e99 100644 --- a/.claude/skills/git-workflow/SKILL.md +++ b/.claude/skills/git-workflow/SKILL.md @@ -1,352 +1,352 @@ ---- -name: git-workflow -description: This skill should be used when creating branches, writing commits, or creating pull requests. Enforces ISA-Frontend Git conventions including feature/task-id-name branch format, conventional commits without co-author tags, and PRs targeting develop branch. ---- - -# Git Workflow Skill - -Enforces Git workflow conventions specific to the ISA-Frontend project. - -## When to Use - -- Creating new branches for features or bugfixes -- Writing commit messages -- Creating pull requests -- Any Git operations requiring adherence to project conventions - -## Core Principles - -### 1. Default Branch is `develop` (NOT `main`) - -- **All PRs target**: `develop` branch -- **Feature branches from**: `develop` -- **Never push directly to**: `develop` or `main` - -### 2. Branch Naming Convention - -**Format**: `/{task-id}-{short-description}` - -**Types**: -- `feature/` - New features or enhancements -- `bugfix/` - Bug fixes -- `hotfix/` - Emergency production fixes - -**Rules**: -- Use English kebab-case for descriptions -- Start with task/issue ID (e.g., `5391`) -- Keep description concise - shorten if too long -- Use hyphens to separate words - -**Examples**: -```bash -# Good -feature/5391-praemie-checkout-action-card-delivery-order -bugfix/6123-fix-login-redirect-loop -hotfix/7890-critical-payment-error - -# Bad -feature/praemie-checkout # Missing task ID -feature/5391_praemie # Using underscores -feature-5391-very-long-description-that-goes-on-forever # Too long -``` - -### 3. Conventional Commits (WITHOUT Co-Author Tags) - -**Format**: `(): ` - -**Types**: -- `feat`: New feature -- `fix`: Bug fix -- `docs`: Documentation only -- `style`: Code style (formatting, missing semicolons) -- `refactor`: Code restructuring without feature changes -- `perf`: Performance improvements -- `test`: Adding or updating tests -- `build`: Build system or dependencies -- `ci`: CI configuration -- `chore`: Maintenance tasks - -**Rules**: -- ❌ **NO** "Generated with Claude Code" tags -- ❌ **NO** "Co-Authored-By: Claude" tags -- βœ… Keep first line under 72 characters -- βœ… Use imperative mood ("add" not "added") -- βœ… Body optional but recommended for complex changes - -**Examples**: -```bash -# Good -feat(checkout): add bonus card selection for delivery orders - -fix(crm): resolve customer search filter reset issue - -refactor(oms): extract return validation logic into service - -# Bad -feat(checkout): add bonus card selection - -Generated with Claude Code -Co-Authored-By: Claude - -# Also bad -Added new feature # Wrong tense -Fix bug # Missing scope -``` - -### 4. Pull Request Creation - -**Target Branch**: Always `develop` - -**PR Title Format**: Same as conventional commit -``` -feat(domain): concise description of changes -``` - -**PR Body Structure**: -```markdown -## Summary -- Brief bullet points of changes - -## Related Tasks -- Closes #{task-id} -- Refs #{related-task} - -## Test Plan -- [ ] Unit tests added/updated -- [ ] E2E attributes added -- [ ] Manual testing completed - -## Breaking Changes -None / List breaking changes - -## Screenshots (if UI changes) -[Add screenshots] -``` - -## Common Workflows - -### Creating a Feature Branch - -```bash -# 1. Update develop -git checkout develop -git pull origin develop - -# 2. Create feature branch -git checkout -b feature/5391-praemie-checkout-action-card - -# 3. Work and commit -git add . -git commit -m "feat(checkout): add primary bonus card selection logic" - -# 4. Push to remote -git push -u origin feature/5391-praemie-checkout-action-card - -# 5. Create PR targeting develop (use gh CLI or web UI) -``` - -### Creating a Bugfix Branch - -```bash -# From develop -git checkout develop -git pull origin develop -git checkout -b bugfix/6123-login-redirect-loop - -# Commit -git commit -m "fix(auth): resolve infinite redirect on logout" -``` - -### Creating a Hotfix Branch - -```bash -# From main (production) -git checkout main -git pull origin main -git checkout -b hotfix/7890-payment-processing-error - -# Commit -git commit -m "fix(checkout): critical payment API timeout handling" - -# Merge to both main and develop -``` - -## Commit Message Guidelines - -### Good Commit Messages - -```bash -feat(crm): add customer loyalty tier calculation - -Implements three-tier loyalty system based on annual spend. -Includes migration for existing customer data. - -Refs #5234 - ---- - -fix(oms): prevent duplicate return submissions - -Adds debouncing to return form submission and validates -against existing returns in the last 60 seconds. - -Closes #5891 - ---- - -refactor(catalogue): extract product search into dedicated service - -Moves search logic from component to ProductSearchService -for better testability and reusability. - ---- - -perf(remission): optimize remission list query with pagination - -Reduces initial load time from 3s to 800ms by implementing -cursor-based pagination. - -Closes #6234 -``` - -### Bad Commit Messages - -```bash -# Too vague -fix: bug fixes - -# Missing scope -feat: new feature - -# Wrong tense -fixed the login issue - -# Including banned tags -feat(checkout): add feature - -Generated with Claude Code -Co-Authored-By: Claude -``` - -## Git Configuration Checks - -### Verify Git Setup - -```bash -# Check current branch -git branch --show-current - -# Verify remote -git remote -v # Should show origin pointing to ISA-Frontend - -# Check for uncommitted changes -git status -``` - -## Common Mistakes to Avoid - -```bash -# ❌ Creating PR against main -gh pr create --base main # WRONG - -# βœ… Always target develop -gh pr create --base develop # CORRECT - -# ❌ Using underscores in branch names -git checkout -b feature/5391_my_feature # WRONG - -# βœ… Use hyphens -git checkout -b feature/5391-my-feature # CORRECT - -# ❌ Adding co-author tags -git commit -m "feat: something - -Co-Authored-By: Claude " # WRONG - -# βœ… Clean commit message -git commit -m "feat(scope): something" # CORRECT - -# ❌ Forgetting task ID in branch name -git checkout -b feature/new-checkout-flow # WRONG - -# βœ… Include task ID -git checkout -b feature/5391-new-checkout-flow # CORRECT -``` - -## Integration with Claude Code - -When Claude Code creates commits or PRs: - -### Commit Creation -```bash -# Claude uses conventional commits WITHOUT attribution -git commit -m "feat(checkout): implement bonus card selection - -Adds logic for selecting primary bonus card during checkout -for delivery orders. Includes validation and error handling. - -Refs #5391" -``` - -### PR Creation -```bash -# Target develop by default -gh pr create --base develop \ - --title "feat(checkout): implement bonus card selection" \ - --body "## Summary -- Add primary bonus card selection logic -- Implement validation for delivery orders -- Add error handling for API failures - -## Related Tasks -- Closes #5391 - -## Test Plan -- [x] Unit tests added -- [x] E2E attributes added -- [x] Manual testing completed" -``` - -## Branch Cleanup - -### After PR Merge -```bash -# Update develop -git checkout develop -git pull origin develop - -# Delete local feature branch -git branch -d feature/5391-praemie-checkout - -# Delete remote branch (usually done by PR merge) -git push origin --delete feature/5391-praemie-checkout -``` - -## Quick Reference - -```bash -# Branch naming -feature/{task-id}-{description} -bugfix/{task-id}-{description} -hotfix/{task-id}-{description} - -# Commit format -(): - -# Common types -feat, fix, docs, style, refactor, perf, test, build, ci, chore - -# PR target -Always: develop (NOT main) - -# Banned in commits -- "Generated with Claude Code" -- "Co-Authored-By: Claude" -- Any AI attribution -``` - -## Resources - -- [Conventional Commits](https://www.conventionalcommits.org/) -- Project PR template: `.github/pull_request_template.md` -- Code review standards: `.github/review-instructions.md` +--- +name: git-workflow +description: This skill should be used when creating branches, writing commits, or creating pull requests. Enforces ISA-Frontend Git conventions including feature/task-id-name branch format, conventional commits without co-author tags, and PRs targeting develop branch. +--- + +# Git Workflow Skill + +Enforces Git workflow conventions specific to the ISA-Frontend project. + +## When to Use + +- Creating new branches for features or bugfixes +- Writing commit messages +- Creating pull requests +- Any Git operations requiring adherence to project conventions + +## Core Principles + +### 1. Default Branch is `develop` (NOT `main`) + +- **All PRs target**: `develop` branch +- **Feature branches from**: `develop` +- **Never push directly to**: `develop` or `main` + +### 2. Branch Naming Convention + +**Format**: `/{task-id}-{short-description}` + +**Types**: +- `feature/` - New features or enhancements +- `bugfix/` - Bug fixes +- `hotfix/` - Emergency production fixes + +**Rules**: +- Use English kebab-case for descriptions +- Start with task/issue ID (e.g., `5391`) +- Keep description concise - shorten if too long +- Use hyphens to separate words + +**Examples**: +```bash +# Good +feature/5391-praemie-checkout-action-card-delivery-order +bugfix/6123-fix-login-redirect-loop +hotfix/7890-critical-payment-error + +# Bad +feature/praemie-checkout # Missing task ID +feature/5391_praemie # Using underscores +feature-5391-very-long-description-that-goes-on-forever # Too long +``` + +### 3. Conventional Commits (WITHOUT Co-Author Tags) + +**Format**: `(): ` + +**Types**: +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation only +- `style`: Code style (formatting, missing semicolons) +- `refactor`: Code restructuring without feature changes +- `perf`: Performance improvements +- `test`: Adding or updating tests +- `build`: Build system or dependencies +- `ci`: CI configuration +- `chore`: Maintenance tasks + +**Rules**: +- ❌ **NO** "Generated with Claude Code" tags +- ❌ **NO** "Co-Authored-By: Claude" tags +- βœ… Keep first line under 72 characters +- βœ… Use imperative mood ("add" not "added") +- βœ… Body optional but recommended for complex changes + +**Examples**: +```bash +# Good +feat(checkout): add bonus card selection for delivery orders + +fix(crm): resolve customer search filter reset issue + +refactor(oms): extract return validation logic into service + +# Bad +feat(checkout): add bonus card selection + +Generated with Claude Code +Co-Authored-By: Claude + +# Also bad +Added new feature # Wrong tense +Fix bug # Missing scope +``` + +### 4. Pull Request Creation + +**Target Branch**: Always `develop` + +**PR Title Format**: Same as conventional commit +``` +feat(domain): concise description of changes +``` + +**PR Body Structure**: +```markdown +## Summary +- Brief bullet points of changes + +## Related Tasks +- Closes #{task-id} +- Refs #{related-task} + +## Test Plan +- [ ] Unit tests added/updated +- [ ] E2E attributes added +- [ ] Manual testing completed + +## Breaking Changes +None / List breaking changes + +## Screenshots (if UI changes) +[Add screenshots] +``` + +## Common Workflows + +### Creating a Feature Branch + +```bash +# 1. Update develop +git checkout develop +git pull origin develop + +# 2. Create feature branch +git checkout -b feature/5391-praemie-checkout-action-card + +# 3. Work and commit +git add . +git commit -m "feat(checkout): add primary bonus card selection logic" + +# 4. Push to remote +git push -u origin feature/5391-praemie-checkout-action-card + +# 5. Create PR targeting develop (use gh CLI or web UI) +``` + +### Creating a Bugfix Branch + +```bash +# From develop +git checkout develop +git pull origin develop +git checkout -b bugfix/6123-login-redirect-loop + +# Commit +git commit -m "fix(auth): resolve infinite redirect on logout" +``` + +### Creating a Hotfix Branch + +```bash +# From main (production) +git checkout main +git pull origin main +git checkout -b hotfix/7890-payment-processing-error + +# Commit +git commit -m "fix(checkout): critical payment API timeout handling" + +# Merge to both main and develop +``` + +## Commit Message Guidelines + +### Good Commit Messages + +```bash +feat(crm): add customer loyalty tier calculation + +Implements three-tier loyalty system based on annual spend. +Includes migration for existing customer data. + +Refs #5234 + +--- + +fix(oms): prevent duplicate return submissions + +Adds debouncing to return form submission and validates +against existing returns in the last 60 seconds. + +Closes #5891 + +--- + +refactor(catalogue): extract product search into dedicated service + +Moves search logic from component to ProductSearchService +for better testability and reusability. + +--- + +perf(remission): optimize remission list query with pagination + +Reduces initial load time from 3s to 800ms by implementing +cursor-based pagination. + +Closes #6234 +``` + +### Bad Commit Messages + +```bash +# Too vague +fix: bug fixes + +# Missing scope +feat: new feature + +# Wrong tense +fixed the login issue + +# Including banned tags +feat(checkout): add feature + +Generated with Claude Code +Co-Authored-By: Claude +``` + +## Git Configuration Checks + +### Verify Git Setup + +```bash +# Check current branch +git branch --show-current + +# Verify remote +git remote -v # Should show origin pointing to ISA-Frontend + +# Check for uncommitted changes +git status +``` + +## Common Mistakes to Avoid + +```bash +# ❌ Creating PR against main +gh pr create --base main # WRONG + +# βœ… Always target develop +gh pr create --base develop # CORRECT + +# ❌ Using underscores in branch names +git checkout -b feature/5391_my_feature # WRONG + +# βœ… Use hyphens +git checkout -b feature/5391-my-feature # CORRECT + +# ❌ Adding co-author tags +git commit -m "feat: something + +Co-Authored-By: Claude " # WRONG + +# βœ… Clean commit message +git commit -m "feat(scope): something" # CORRECT + +# ❌ Forgetting task ID in branch name +git checkout -b feature/new-checkout-flow # WRONG + +# βœ… Include task ID +git checkout -b feature/5391-new-checkout-flow # CORRECT +``` + +## Integration with Claude Code + +When Claude Code creates commits or PRs: + +### Commit Creation +```bash +# Claude uses conventional commits WITHOUT attribution +git commit -m "feat(checkout): implement bonus card selection + +Adds logic for selecting primary bonus card during checkout +for delivery orders. Includes validation and error handling. + +Refs #5391" +``` + +### PR Creation +```bash +# Target develop by default +gh pr create --base develop \ + --title "feat(checkout): implement bonus card selection" \ + --body "## Summary +- Add primary bonus card selection logic +- Implement validation for delivery orders +- Add error handling for API failures + +## Related Tasks +- Closes #5391 + +## Test Plan +- [x] Unit tests added +- [x] E2E attributes added +- [x] Manual testing completed" +``` + +## Branch Cleanup + +### After PR Merge +```bash +# Update develop +git checkout develop +git pull origin develop + +# Delete local feature branch +git branch -d feature/5391-praemie-checkout + +# Delete remote branch (usually done by PR merge) +git push origin --delete feature/5391-praemie-checkout +``` + +## Quick Reference + +```bash +# Branch naming +feature/{task-id}-{description} +bugfix/{task-id}-{description} +hotfix/{task-id}-{description} + +# Commit format +(): + +# Common types +feat, fix, docs, style, refactor, perf, test, build, ci, chore + +# PR target +Always: develop (NOT main) + +# Banned in commits +- "Generated with Claude Code" +- "Co-Authored-By: Claude" +- Any AI attribution +``` + +## Resources + +- [Conventional Commits](https://www.conventionalcommits.org/) +- Project PR template: `.github/pull_request_template.md` +- Code review standards: `.github/review-instructions.md` diff --git a/.claude/skills/library-creator/SKILL.md b/.claude/skills/library-creator/SKILL.md index 3506d9320..c637730a6 100644 --- a/.claude/skills/library-creator/SKILL.md +++ b/.claude/skills/library-creator/SKILL.md @@ -1,284 +1,277 @@ ---- -name: library-creator -description: This skill should be used when creating feature/data-access/ui/util libraries or user says "create library", "new library", "scaffold library". Creates new Angular libraries in ISA-Frontend monorepo with proper Nx configuration, Vitest setup, architectural tags, and path aliases. ---- - -# Library Creator - -## Overview - -Automate the creation of new Angular libraries following ISA-Frontend conventions. This skill handles the complete scaffolding workflow including Nx generation, Vitest configuration with CI/CD integration, architectural tags, path alias verification, and initial validation. - -## When to Use This Skill - -Invoke this skill when: -- User requests creating a new library -- User mentions "new library", "scaffold library", or "create feature" -- User wants to add a new domain/layer/feature to the monorepo - -## Required Parameters - -Collect these parameters from the user: -- **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils) -- **layer**: Layer type (feature, data-access, ui, util) -- **name**: Library name in kebab-case - -## Library Creation Workflow - -### Step 1: Validate Input - -**Verify Domain:** -- Use `docs-researcher` to check `docs/library-reference.md` -- Ensure domain follows existing patterns - -**Validate Layer:** -- Must be one of: feature, data-access, ui, util - -**Check Name:** -- Must be kebab-case -- Must not conflict with existing libraries - -**Determine Path Depth:** -- 3 levels: `libs/domain/layer/name` β†’ `../../../` -- 4 levels: `libs/domain/type/layer/name` β†’ `../../../../` - -### Step 2: Run Dry-Run - -Execute Nx generator with `--dry-run` to preview changes: - -```bash -npx nx generate @nx/angular:library \ - --name=[domain]-[layer]-[name] \ - --directory=libs/[domain]/[layer]/[name] \ - --importPath=@isa/[domain]/[layer]/[name] \ - --prefix=[domain] \ - --style=css \ - --unitTestRunner=vitest \ - --standalone=true \ - --skipTests=false \ - --dry-run -``` - -Review output with user before proceeding. - -### Step 3: Generate Library - -Execute without `--dry-run`: - -```bash -npx nx generate @nx/angular:library \ - --name=[domain]-[layer]-[name] \ - --directory=libs/[domain]/[layer]/[name] \ - --importPath=@isa/[domain]/[layer]/[name] \ - --prefix=[domain] \ - --style=css \ - --unitTestRunner=vitest \ - --standalone=true \ - --skipTests=false -``` - -### Step 4: Add Architectural Tags - -**CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`. - -Run the tagging script: -```bash -node scripts/add-library-tags.js -``` - -Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`: - -```json -{ - "name": "[domain]-[layer]-[name]", - "tags": [ - "scope:[domain]", - "type:[layer]" - ] -} -``` - -**Tag Rules:** -- **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`) -- **Type tag**: `type:[layer]` (e.g., `type:feature`, `type:data-access`, `type:ui`, `type:util`) - -**Examples:** -- `libs/oms/feature/return-search` β†’ `["scope:oms", "type:feature"]` -- `libs/ui/buttons` β†’ `["scope:ui", "type:ui"]` -- `libs/shared/scanner` β†’ `["scope:shared", "type:shared"]` -- `libs/core/auth` β†’ `["scope:core", "type:core"]` - -**Verification:** -```bash -# Check tags were added -cat libs/[domain]/[layer]/[name]/project.json | jq '.tags' - -# Should output: ["scope:[domain]", "type:[layer]"] -``` - -### Step 5: Configure Vitest with JUnit and Cobertura - -Update `libs/[path]/vite.config.mts` with this template: - -```typescript -/// -import { defineConfig } from 'vite'; -import angular from '@analogjs/vite-plugin-angular'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; - -export default -// @ts-expect-error - Vitest reporter tuple types have complex inference issues -defineConfig(() => ({ - root: __dirname, - cacheDir: '../../../node_modules/.vite/libs/[path]', - plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - test: { - watch: false, - globals: true, - environment: 'jsdom', - include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['src/test-setup.ts'], - reporters: [ - 'default', - ['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }], - ], - coverage: { - reportsDirectory: '../../../coverage/libs/[path]', - provider: 'v8' as const, - reporter: ['text', 'cobertura'], - }, - }, -})); -``` - -**Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location. - -### Step 6: Verify Configuration - -**Check Path Alias:** -- Verify `tsconfig.base.json` was updated -- Should have: `"@isa/[domain]/[layer]/[name]": ["libs/[domain]/[layer]/[name]/src/index.ts"]` - -**Run Initial Test:** -```bash -npx nx test [library-name] --coverage.enabled=true --skip-nx-cache -``` - -**Verify CI/CD Files Created:** -- JUnit XML: `testresults/junit-[library-name].xml` -- Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml` - -### Step 7: Create Library README - -Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including: -- Overview and purpose -- Installation/import instructions -- API documentation -- Usage examples -- Testing information (Vitest + Angular Testing Utilities) - -### Step 8: Update Library Reference - -Add entry to `docs/library-reference.md` under appropriate domain: - -```markdown -#### `@isa/[domain]/[layer]/[name]` -**Path:** `libs/[domain]/[layer]/[name]` -**Type:** [Feature/Data Access/UI/Util] -**Testing:** Vitest - -[Brief description] -``` - -### Step 9: Run Full Validation - -Execute validation commands to ensure library is properly configured: - -```bash -# Lint (includes boundary checks) -npx nx lint [library-name] - -# Test with coverage -npx nx test [library-name] --coverage.enabled=true --skip-nx-cache - -# Build (if buildable) -npx nx build [library-name] - -# Dependency graph -npx nx graph --focus=[library-name] -``` - -### Step 10: Generate Creation Report - -Provide this structured report to the user: - -``` -Library Created Successfully -============================ - -Library Name: [domain]-[layer]-[name] -Path: libs/[domain]/[layer]/[name] -Import Alias: @isa/[domain]/[layer]/[name] - -βœ… Configuration ----------------- -Test Framework: Vitest with Angular Testing Utilities -Style: CSS -Standalone: Yes -Tags: scope:[domain], type:[layer] -JUnit Reporter: βœ… testresults/junit-[library-name].xml -Cobertura Coverage: βœ… coverage/libs/[path]/cobertura-coverage.xml - -πŸ“¦ Import Statement -------------------- -import { Component } from '@isa/[domain]/[layer]/[name]'; - -πŸ§ͺ Test Commands ----------------- -npx nx test [library-name] --skip-nx-cache -npx nx test [library-name] --coverage.enabled=true --skip-nx-cache - -πŸ—οΈ Architecture Compliance --------------------------- -Tags enforce module boundaries via @nx/enforce-module-boundaries -Run lint to check for violations: npx nx lint [library-name] - -πŸ“ Next Steps -------------- -1. Develop library features -2. Write tests using Vitest + Angular Testing Utilities -3. Add E2E attributes (data-what, data-which) to templates -4. Update README with usage examples -5. Follow architecture rules (see eslint.config.js for constraints) -``` - -## Error Handling - -**Path depth mismatch:** -- Count directory levels from workspace root -- Adjust `../` in outputFile and reportsDirectory - -**TypeScript errors in vite.config.mts:** -- Add `// @ts-expect-error` before `defineConfig()` - -**Path alias not working:** -- Check tsconfig.base.json -- Run `npx nx reset` -- Restart TypeScript server - -**Library already exists:** -- Check `tsconfig.base.json` for existing path alias -- Use Grep to search for existing library name -- Suggest alternative name to user - -## References - -- docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections) -- docs/library-reference.md (domain patterns) -- CLAUDE.md (Library Organization, Testing Framework sections) -- eslint.config.js (@nx/enforce-module-boundaries configuration) -- scripts/add-library-tags.js (automatic tagging script) -- .claude/skills/architecture-enforcer (boundary validation) -- Nx Angular Library Generator: https://nx.dev/nx-api/angular/generators/library -- Nx Enforce Module Boundaries: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module-boundaries +--- +name: library-creator +description: This skill should be used when creating feature/data-access/ui/util libraries or user says "create library", "new library", "scaffold library". Creates new Angular libraries in ISA-Frontend monorepo with proper Nx configuration, Vitest setup, architectural tags, and path aliases. +--- + +# Library Creator + +## Overview + +Automate the creation of new Angular libraries following ISA-Frontend conventions. This skill handles the complete scaffolding workflow including Nx generation, Vitest configuration with CI/CD integration, architectural tags, path alias verification, and initial validation. + +## Required Parameters + +Collect these parameters from the user: +- **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils) +- **layer**: Layer type (feature, data-access, ui, util) +- **name**: Library name in kebab-case + +## Library Creation Workflow + +### Step 1: Validate Input + +**Verify Domain:** +- Use `docs-researcher` to check `docs/library-reference.md` +- Ensure domain follows existing patterns + +**Validate Layer:** +- Must be one of: feature, data-access, ui, util + +**Check Name:** +- Must be kebab-case +- Must not conflict with existing libraries + +**Determine Path Depth:** +- 3 levels: `libs/domain/layer/name` β†’ `../../../` +- 4 levels: `libs/domain/type/layer/name` β†’ `../../../../` + +### Step 2: Run Dry-Run + +Execute Nx generator with `--dry-run` to preview changes: + +```bash +npx nx generate @nx/angular:library \ + --name=[domain]-[layer]-[name] \ + --directory=libs/[domain]/[layer]/[name] \ + --importPath=@isa/[domain]/[layer]/[name] \ + --prefix=[domain] \ + --style=css \ + --unitTestRunner=vitest \ + --standalone=true \ + --skipTests=false \ + --dry-run +``` + +Review output with user before proceeding. + +### Step 3: Generate Library + +Execute without `--dry-run`: + +```bash +npx nx generate @nx/angular:library \ + --name=[domain]-[layer]-[name] \ + --directory=libs/[domain]/[layer]/[name] \ + --importPath=@isa/[domain]/[layer]/[name] \ + --prefix=[domain] \ + --style=css \ + --unitTestRunner=vitest \ + --standalone=true \ + --skipTests=false +``` + +### Step 4: Add Architectural Tags + +**CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`. + +Run the tagging script: +```bash +node scripts/add-library-tags.js +``` + +Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`: + +```json +{ + "name": "[domain]-[layer]-[name]", + "tags": [ + "scope:[domain]", + "type:[layer]" + ] +} +``` + +**Tag Rules:** +- **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`) +- **Type tag**: `type:[layer]` (e.g., `type:feature`, `type:data-access`, `type:ui`, `type:util`) + +**Examples:** +- `libs/oms/feature/return-search` β†’ `["scope:oms", "type:feature"]` +- `libs/ui/buttons` β†’ `["scope:ui", "type:ui"]` +- `libs/shared/scanner` β†’ `["scope:shared", "type:shared"]` +- `libs/core/auth` β†’ `["scope:core", "type:core"]` + +**Verification:** +```bash +# Check tags were added +cat libs/[domain]/[layer]/[name]/project.json | jq '.tags' + +# Should output: ["scope:[domain]", "type:[layer]"] +``` + +### Step 5: Configure Vitest with JUnit and Cobertura + +Update `libs/[path]/vite.config.mts` with this template: + +```typescript +/// +import { defineConfig } from 'vite'; +import angular from '@analogjs/vite-plugin-angular'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default +// @ts-expect-error - Vitest reporter tuple types have complex inference issues +defineConfig(() => ({ + root: __dirname, + cacheDir: '../../../node_modules/.vite/libs/[path]', + plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: ['src/test-setup.ts'], + reporters: [ + 'default', + ['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }], + ], + coverage: { + reportsDirectory: '../../../coverage/libs/[path]', + provider: 'v8' as const, + reporter: ['text', 'cobertura'], + }, + }, +})); +``` + +**Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location. + +### Step 6: Verify Configuration + +**Check Path Alias:** +- Verify `tsconfig.base.json` was updated +- Should have: `"@isa/[domain]/[layer]/[name]": ["libs/[domain]/[layer]/[name]/src/index.ts"]` + +**Run Initial Test:** +```bash +npx nx test [library-name] --coverage.enabled=true --skip-nx-cache +``` + +**Verify CI/CD Files Created:** +- JUnit XML: `testresults/junit-[library-name].xml` +- Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml` + +### Step 7: Create Library README + +Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including: +- Overview and purpose +- Installation/import instructions +- API documentation +- Usage examples +- Testing information (Vitest + Angular Testing Utilities) + +### Step 8: Update Library Reference + +Add entry to `docs/library-reference.md` under appropriate domain: + +```markdown +#### `@isa/[domain]/[layer]/[name]` +**Path:** `libs/[domain]/[layer]/[name]` +**Type:** [Feature/Data Access/UI/Util] +**Testing:** Vitest + +[Brief description] +``` + +### Step 9: Run Full Validation + +Execute validation commands to ensure library is properly configured: + +```bash +# Lint (includes boundary checks) +npx nx lint [library-name] + +# Test with coverage +npx nx test [library-name] --coverage.enabled=true --skip-nx-cache + +# Build (if buildable) +npx nx build [library-name] + +# Dependency graph +npx nx graph --focus=[library-name] +``` + +### Step 10: Generate Creation Report + +Provide this structured report to the user: + +``` +Library Created Successfully +============================ + +Library Name: [domain]-[layer]-[name] +Path: libs/[domain]/[layer]/[name] +Import Alias: @isa/[domain]/[layer]/[name] + +βœ… Configuration +---------------- +Test Framework: Vitest with Angular Testing Utilities +Style: CSS +Standalone: Yes +Tags: scope:[domain], type:[layer] +JUnit Reporter: βœ… testresults/junit-[library-name].xml +Cobertura Coverage: βœ… coverage/libs/[path]/cobertura-coverage.xml + +πŸ“¦ Import Statement +------------------- +import { Component } from '@isa/[domain]/[layer]/[name]'; + +πŸ§ͺ Test Commands +---------------- +npx nx test [library-name] --skip-nx-cache +npx nx test [library-name] --coverage.enabled=true --skip-nx-cache + +πŸ—οΈ Architecture Compliance +-------------------------- +Tags enforce module boundaries via @nx/enforce-module-boundaries +Run lint to check for violations: npx nx lint [library-name] + +πŸ“ Next Steps +------------- +1. Develop library features +2. Write tests using Vitest + Angular Testing Utilities +3. Add E2E attributes (data-what, data-which) to templates +4. Update README with usage examples +5. Follow architecture rules (see eslint.config.js for constraints) +``` + +## Error Handling + +**Path depth mismatch:** +- Count directory levels from workspace root +- Adjust `../` in outputFile and reportsDirectory + +**TypeScript errors in vite.config.mts:** +- Add `// @ts-expect-error` before `defineConfig()` + +**Path alias not working:** +- Check tsconfig.base.json +- Run `npx nx reset` +- Restart TypeScript server + +**Library already exists:** +- Check `tsconfig.base.json` for existing path alias +- Use Grep to search for existing library name +- Suggest alternative name to user + +## References + +- docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections) +- docs/library-reference.md (domain patterns) +- CLAUDE.md (Library Organization, Testing Framework sections) +- eslint.config.js (@nx/enforce-module-boundaries configuration) +- scripts/add-library-tags.js (automatic tagging script) +- .claude/skills/architecture-validator (boundary validation) +- Nx Angular Library Generator: https://nx.dev/nx-api/angular/generators/library +- Nx Enforce Module Boundaries: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module-boundaries diff --git a/.claude/skills/logging/SKILL.md b/.claude/skills/logging/SKILL.md index d3937a374..f498d6b3a 100644 --- a/.claude/skills/logging/SKILL.md +++ b/.claude/skills/logging/SKILL.md @@ -1,272 +1,234 @@ ---- -name: logging -description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes. ---- - -# Logging Helper Skill - -Ensures consistent and efficient logging using `@isa/core/logging` library. - -## When to Use - -- Adding logging to new components/services -- Refactoring existing logging code -- Reviewing code for proper logging patterns -- Debugging logging issues - -## Core Principles - -### 1. Always Use Factory Pattern - -```typescript -import { logger } from '@isa/core/logging'; - -// βœ… DO -#logger = logger(); - -// ❌ DON'T -constructor(private loggingService: LoggingService) {} -``` - -### 2. Choose Appropriate Log Levels - -- **Trace**: Fine-grained debugging (method entry/exit) -- **Debug**: Development debugging (variable states) -- **Info**: Runtime information (user actions, events) -- **Warn**: Potentially harmful situations -- **Error**: Errors affecting functionality - -### 3. Context Patterns - -**Static Context** (component level): -```typescript -#logger = logger({ component: 'UserProfileComponent' }); -``` - -**Dynamic Context** (instance level): -```typescript -#logger = logger(() => ({ - userId: this.authService.currentUserId, - storeId: this.config.storeId -})); -``` - -**Message Context** (use functions for performance): -```typescript -// βœ… Recommended - lazy evaluation -this.#logger.info('Order processed', () => ({ - orderId: order.id, - total: order.total, - timestamp: Date.now() -})); - -// βœ… Acceptable - static values -this.#logger.info('Order processed', { - orderId: order.id, - status: 'completed' -}); -``` - -## Essential Patterns - -### Component Logging -```typescript -@Component({ - selector: 'app-product-list', - standalone: true, -}) -export class ProductListComponent { - #logger = logger({ component: 'ProductListComponent' }); - - ngOnInit(): void { - this.#logger.info('Component initialized'); - } - - onAction(id: string): void { - this.#logger.debug('Action triggered', { id }); - } -} -``` - -### Service Logging -```typescript -@Injectable({ providedIn: 'root' }) -export class DataService { - #logger = logger({ service: 'DataService' }); - - fetchData(endpoint: string): Observable { - this.#logger.debug('Fetching data', { endpoint }); - - return this.http.get(endpoint).pipe( - tap((data) => this.#logger.info('Data fetched', () => ({ - endpoint, - size: data.length - }))), - catchError((error) => { - this.#logger.error('Fetch failed', error, () => ({ - endpoint, - status: error.status - })); - return throwError(() => error); - }) - ); - } -} -``` - -### Error Handling -```typescript -try { - await this.processOrder(orderId); -} catch (error) { - this.#logger.error('Order processing failed', error as Error, () => ({ - orderId, - step: this.currentStep, - attemptNumber: this.retryCount - })); - throw error; -} -``` - -### Hierarchical Context -```typescript -@Component({ - providers: [ - provideLoggerContext({ feature: 'checkout', module: 'sales' }) - ] -}) -export class CheckoutComponent { - #logger = logger(() => ({ userId: this.userService.currentUserId })); - - // Logs include: feature, module, userId + message context -} -``` - -## Common Mistakes to Avoid - -```typescript -// ❌ Don't use console.log -console.log('User logged in'); -// βœ… Use logger -this.#logger.info('User logged in'); - -// ❌ Don't create expensive context eagerly -this.#logger.debug('Processing', { - data: this.computeExpensive() // Always executes -}); -// βœ… Use function for lazy evaluation -this.#logger.debug('Processing', () => ({ - data: this.computeExpensive() // Only if debug enabled -})); - -// ❌ Don't log in tight loops -for (const item of items) { - this.#logger.debug('Item', { item }); -} -// βœ… Log aggregates -this.#logger.debug('Batch processed', () => ({ - count: items.length -})); - -// ❌ Don't log sensitive data -this.#logger.info('User auth', { password: user.password }); -// βœ… Log safe identifiers only -this.#logger.info('User auth', { userId: user.id }); - -// ❌ Don't miss error object -this.#logger.error('Failed'); -// βœ… Include error object -this.#logger.error('Failed', error as Error); -``` - -## Configuration - -### App Configuration -```typescript -// app.config.ts -import { ApplicationConfig, isDevMode } from '@angular/core'; -import { - provideLogging, withLogLevel, withSink, - LogLevel, ConsoleLogSink -} from '@isa/core/logging'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideLogging( - withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn), - withSink(ConsoleLogSink), - withContext({ app: 'ISA', version: '1.0.0' }) - ) - ] -}; -``` - -## Testing - -```typescript -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { LoggingService } from '@isa/core/logging'; - -describe('MyComponent', () => { - const createComponent = createComponentFactory({ - component: MyComponent, - mocks: [LoggingService] - }); - - it('should log error', () => { - const spectator = createComponent(); - const loggingService = spectator.inject(LoggingService); - - spectator.component.riskyOperation(); - - expect(loggingService.error).toHaveBeenCalledWith( - expect.any(String), - expect.any(Error), - expect.any(Function) - ); - }); -}); -``` - -## Code Review Checklist - -- [ ] Uses `logger()` factory, not `LoggingService` injection -- [ ] Appropriate log level for each message -- [ ] Context functions for expensive operations -- [ ] No sensitive information (passwords, tokens, PII) -- [ ] No `console.log` statements -- [ ] Error logs include error object -- [ ] No logging in tight loops -- [ ] Component/service identified in context -- [ ] E2E attributes on interactive elements - -## Quick Reference - -```typescript -// Import -import { logger, provideLoggerContext } from '@isa/core/logging'; - -// Create logger -#logger = logger(); // Basic -#logger = logger({ component: 'Name' }); // Static context -#logger = logger(() => ({ id: this.id })); // Dynamic context - -// Log messages -this.#logger.trace('Detailed trace'); -this.#logger.debug('Debug info'); -this.#logger.info('General info', () => ({ key: value })); -this.#logger.warn('Warning'); -this.#logger.error('Error', error, () => ({ context })); - -// Component context -@Component({ - providers: [provideLoggerContext({ feature: 'users' })] -}) -``` - -## Additional Resources - -- Full documentation: `libs/core/logging/README.md` -- Examples: `.claude/skills/logging-helper/examples.md` -- Quick reference: `.claude/skills/logging-helper/reference.md` -- Troubleshooting: `.claude/skills/logging-helper/troubleshooting.md` +--- +name: logging +description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes. +--- + +# Logging + +Ensures consistent and efficient logging using `@isa/core/logging` library. + +## Core Principles + +### 1. Always Use Factory Pattern + +```typescript +import { logger } from '@isa/core/logging'; + +// βœ… DO +#logger = logger(); + +// ❌ DON'T +constructor(private loggingService: LoggingService) {} +``` + +### 2. Choose Appropriate Log Levels + +- **Trace**: Fine-grained debugging (method entry/exit) +- **Debug**: Development debugging (variable states) +- **Info**: Runtime information (user actions, events) +- **Warn**: Potentially harmful situations +- **Error**: Errors affecting functionality + +### 3. Context Patterns + +**Static Context** (component level): +```typescript +#logger = logger({ component: 'UserProfileComponent' }); +``` + +**Dynamic Context** (instance level): +```typescript +#logger = logger(() => ({ + userId: this.authService.currentUserId, + storeId: this.config.storeId +})); +``` + +**Message Context** (use functions for performance): +```typescript +// βœ… Recommended - lazy evaluation +this.#logger.info('Order processed', () => ({ + orderId: order.id, + total: order.total, + timestamp: Date.now() +})); + +// βœ… Acceptable - static values +this.#logger.info('Order processed', { + orderId: order.id, + status: 'completed' +}); +``` + +## Essential Patterns + +### Component Logging +```typescript +@Component({ + selector: 'app-product-list', + standalone: true, +}) +export class ProductListComponent { + #logger = logger({ component: 'ProductListComponent' }); + + ngOnInit(): void { + this.#logger.info('Component initialized'); + } + + onAction(id: string): void { + this.#logger.debug('Action triggered', { id }); + } +} +``` + +### Service Logging +```typescript +@Injectable({ providedIn: 'root' }) +export class DataService { + #logger = logger({ service: 'DataService' }); + + fetchData(endpoint: string): Observable { + this.#logger.debug('Fetching data', { endpoint }); + + return this.http.get(endpoint).pipe( + tap((data) => this.#logger.info('Data fetched', () => ({ + endpoint, + size: data.length + }))), + catchError((error) => { + this.#logger.error('Fetch failed', error, () => ({ + endpoint, + status: error.status + })); + return throwError(() => error); + }) + ); + } +} +``` + +### Error Handling +```typescript +try { + await this.processOrder(orderId); +} catch (error) { + this.#logger.error('Order processing failed', error as Error, () => ({ + orderId, + step: this.currentStep, + attemptNumber: this.retryCount + })); + throw error; +} +``` + +### Hierarchical Context +```typescript +@Component({ + providers: [ + provideLoggerContext({ feature: 'checkout', module: 'sales' }) + ] +}) +export class CheckoutComponent { + #logger = logger(() => ({ userId: this.userService.currentUserId })); + + // Logs include: feature, module, userId + message context +} +``` + +## Common Mistakes to Avoid + +```typescript +// ❌ Don't use console.log +console.log('User logged in'); +// βœ… Use logger +this.#logger.info('User logged in'); + +// ❌ Don't create expensive context eagerly +this.#logger.debug('Processing', { + data: this.computeExpensive() // Always executes +}); +// βœ… Use function for lazy evaluation +this.#logger.debug('Processing', () => ({ + data: this.computeExpensive() // Only if debug enabled +})); + +// ❌ Don't log in tight loops +for (const item of items) { + this.#logger.debug('Item', { item }); +} +// βœ… Log aggregates +this.#logger.debug('Batch processed', () => ({ + count: items.length +})); + +// ❌ Don't log sensitive data +this.#logger.info('User auth', { password: user.password }); +// βœ… Log safe identifiers only +this.#logger.info('User auth', { userId: user.id }); + +// ❌ Don't miss error object +this.#logger.error('Failed'); +// βœ… Include error object +this.#logger.error('Failed', error as Error); +``` + +## Configuration + +### App Configuration +```typescript +// app.config.ts +import { ApplicationConfig, isDevMode } from '@angular/core'; +import { + provideLogging, withLogLevel, withSink, + LogLevel, ConsoleLogSink +} from '@isa/core/logging'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideLogging( + withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn), + withSink(ConsoleLogSink), + withContext({ app: 'ISA', version: '1.0.0' }) + ) + ] +}; +``` + +## Testing + +```typescript +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { LoggingService } from '@isa/core/logging'; + +describe('MyComponent', () => { + const createComponent = createComponentFactory({ + component: MyComponent, + mocks: [LoggingService] + }); + + it('should log error', () => { + const spectator = createComponent(); + const loggingService = spectator.inject(LoggingService); + + spectator.component.riskyOperation(); + + expect(loggingService.error).toHaveBeenCalledWith( + expect.any(String), + expect.any(Error), + expect.any(Function) + ); + }); +}); +``` + +## Additional Resources + +**For more detailed information:** + +- **API signatures and patterns**: See [references/api-reference.md](references/api-reference.md) for complete API documentation +- **Real-world examples**: See [references/examples.md](references/examples.md) for components, services, guards, interceptors, and more +- **Troubleshooting**: See [references/troubleshooting.md](references/troubleshooting.md) for common issues and solutions + +**Project documentation:** + +- Full library documentation: `libs/core/logging/README.md` diff --git a/.claude/skills/logging/reference.md b/.claude/skills/logging/references/api-reference.md similarity index 95% rename from .claude/skills/logging/reference.md rename to .claude/skills/logging/references/api-reference.md index 54b138865..fa8ee942d 100644 --- a/.claude/skills/logging/reference.md +++ b/.claude/skills/logging/references/api-reference.md @@ -1,192 +1,192 @@ -# Logging Quick Reference - -## API Signatures - -```typescript -// Factory -function logger(ctx?: MaybeLoggerContextFn): LoggerApi - -// Logger API -interface LoggerApi { - trace(message: string, context?: MaybeLoggerContextFn): void; - debug(message: string, context?: MaybeLoggerContextFn): void; - info(message: string, context?: MaybeLoggerContextFn): void; - warn(message: string, context?: MaybeLoggerContextFn): void; - error(message: string, error?: Error, context?: MaybeLoggerContextFn): void; -} - -// Types -type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext); -interface LoggerContext { [key: string]: unknown; } -``` - -## Common Patterns - -| Pattern | Code | -|---------|------| -| Basic logger | `#logger = logger()` | -| Static context | `#logger = logger({ component: 'Name' })` | -| Dynamic context | `#logger = logger(() => ({ id: this.id }))` | -| Log info | `this.#logger.info('Message')` | -| Log with context | `this.#logger.info('Message', () => ({ key: value }))` | -| Log error | `this.#logger.error('Error', error)` | -| Error with context | `this.#logger.error('Error', error, () => ({ id }))` | -| Component context | `providers: [provideLoggerContext({ feature: 'x' })]` | - -## Configuration - -```typescript -// app.config.ts -import { provideLogging, withLogLevel, withSink, withContext, - LogLevel, ConsoleLogSink } from '@isa/core/logging'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideLogging( - withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn), - withSink(ConsoleLogSink), - withContext({ app: 'ISA', version: '1.0.0' }) - ) - ] -}; -``` - -## Log Levels - -| Level | Use Case | Example | -|-------|----------|---------| -| `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` | -| `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` | -| `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` | -| `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` | -| `Error` | Errors | `this.#logger.error('Operation failed', error)` | -| `Off` | Disable logging | `withLogLevel(LogLevel.Off)` | - -## Decision Trees - -### Context Type Decision -``` -Value changes at runtime? -β”œβ”€ Yes β†’ () => ({ value: this.getValue() }) -└─ No β†’ { value: 'static' } - -Computing value is expensive? -β”œβ”€ Yes β†’ () => ({ data: this.compute() }) -└─ No β†’ Either works -``` - -### Log Level Decision -``` -Method flow details? β†’ Trace -Development debug? β†’ Debug -Runtime information? β†’ Info -Potential problem? β†’ Warn -Error occurred? β†’ Error -``` - -## Performance Tips - -```typescript -// βœ… DO: Lazy evaluation -this.#logger.debug('Data', () => ({ - result: this.expensive() // Only runs if debug enabled -})); - -// ❌ DON'T: Eager evaluation -this.#logger.debug('Data', { - result: this.expensive() // Always runs -}); - -// βœ… DO: Log aggregates -this.#logger.info('Batch done', () => ({ count: items.length })); - -// ❌ DON'T: Log in loops -for (const item of items) { - this.#logger.debug('Item', { item }); // Performance hit -} -``` - -## Testing - -```typescript -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { LoggingService } from '@isa/core/logging'; - -describe('MyComponent', () => { - const createComponent = createComponentFactory({ - component: MyComponent, - mocks: [LoggingService] - }); - - it('logs error', () => { - const spectator = createComponent(); - const logger = spectator.inject(LoggingService); - - spectator.component.operation(); - - expect(logger.error).toHaveBeenCalled(); - }); -}); -``` - -## Custom Sink - -```typescript -import { Injectable } from '@angular/core'; -import { Sink, LogLevel, LoggerContext } from '@isa/core/logging'; - -@Injectable() -export class CustomSink implements Sink { - log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void { - // Implementation - } -} - -// Register -provideLogging(withSink(CustomSink)) -``` - -## Sink Function (with DI) - -```typescript -import { inject } from '@angular/core'; -import { SinkFn, LogLevel } from '@isa/core/logging'; - -export const remoteSink: SinkFn = () => { - const http = inject(HttpClient); - - return (level, message, context, error) => { - if (level === LogLevel.Error) { - http.post('/api/logs', { level, message, context, error }).subscribe(); - } - }; -}; - -// Register -provideLogging(withSinkFn(remoteSink)) -``` - -## Common Imports - -```typescript -// Main imports -import { logger, provideLoggerContext } from '@isa/core/logging'; - -// Configuration imports -import { - provideLogging, - withLogLevel, - withSink, - withContext, - LogLevel, - ConsoleLogSink -} from '@isa/core/logging'; - -// Type imports -import { - LoggerApi, - Sink, - SinkFn, - LoggerContext -} from '@isa/core/logging'; -``` +# Logging API Reference + +## API Signatures + +```typescript +// Factory +function logger(ctx?: MaybeLoggerContextFn): LoggerApi + +// Logger API +interface LoggerApi { + trace(message: string, context?: MaybeLoggerContextFn): void; + debug(message: string, context?: MaybeLoggerContextFn): void; + info(message: string, context?: MaybeLoggerContextFn): void; + warn(message: string, context?: MaybeLoggerContextFn): void; + error(message: string, error?: Error, context?: MaybeLoggerContextFn): void; +} + +// Types +type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext); +interface LoggerContext { [key: string]: unknown; } +``` + +## Common Patterns + +| Pattern | Code | +|---------|------| +| Basic logger | `#logger = logger()` | +| Static context | `#logger = logger({ component: 'Name' })` | +| Dynamic context | `#logger = logger(() => ({ id: this.id }))` | +| Log info | `this.#logger.info('Message')` | +| Log with context | `this.#logger.info('Message', () => ({ key: value }))` | +| Log error | `this.#logger.error('Error', error)` | +| Error with context | `this.#logger.error('Error', error, () => ({ id }))` | +| Component context | `providers: [provideLoggerContext({ feature: 'x' })]` | + +## Configuration + +```typescript +// app.config.ts +import { provideLogging, withLogLevel, withSink, withContext, + LogLevel, ConsoleLogSink } from '@isa/core/logging'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideLogging( + withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn), + withSink(ConsoleLogSink), + withContext({ app: 'ISA', version: '1.0.0' }) + ) + ] +}; +``` + +## Log Levels + +| Level | Use Case | Example | +|-------|----------|---------| +| `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` | +| `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` | +| `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` | +| `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` | +| `Error` | Errors | `this.#logger.error('Operation failed', error)` | +| `Off` | Disable logging | `withLogLevel(LogLevel.Off)` | + +## Decision Trees + +### Context Type Decision +``` +Value changes at runtime? +β”œβ”€ Yes β†’ () => ({ value: this.getValue() }) +└─ No β†’ { value: 'static' } + +Computing value is expensive? +β”œβ”€ Yes β†’ () => ({ data: this.compute() }) +└─ No β†’ Either works +``` + +### Log Level Decision +``` +Method flow details? β†’ Trace +Development debug? β†’ Debug +Runtime information? β†’ Info +Potential problem? β†’ Warn +Error occurred? β†’ Error +``` + +## Performance Tips + +```typescript +// βœ… DO: Lazy evaluation +this.#logger.debug('Data', () => ({ + result: this.expensive() // Only runs if debug enabled +})); + +// ❌ DON'T: Eager evaluation +this.#logger.debug('Data', { + result: this.expensive() // Always runs +}); + +// βœ… DO: Log aggregates +this.#logger.info('Batch done', () => ({ count: items.length })); + +// ❌ DON'T: Log in loops +for (const item of items) { + this.#logger.debug('Item', { item }); // Performance hit +} +``` + +## Testing + +```typescript +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { LoggingService } from '@isa/core/logging'; + +describe('MyComponent', () => { + const createComponent = createComponentFactory({ + component: MyComponent, + mocks: [LoggingService] + }); + + it('logs error', () => { + const spectator = createComponent(); + const logger = spectator.inject(LoggingService); + + spectator.component.operation(); + + expect(logger.error).toHaveBeenCalled(); + }); +}); +``` + +## Custom Sink + +```typescript +import { Injectable } from '@angular/core'; +import { Sink, LogLevel, LoggerContext } from '@isa/core/logging'; + +@Injectable() +export class CustomSink implements Sink { + log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void { + // Implementation + } +} + +// Register +provideLogging(withSink(CustomSink)) +``` + +## Sink Function (with DI) + +```typescript +import { inject } from '@angular/core'; +import { SinkFn, LogLevel } from '@isa/core/logging'; + +export const remoteSink: SinkFn = () => { + const http = inject(HttpClient); + + return (level, message, context, error) => { + if (level === LogLevel.Error) { + http.post('/api/logs', { level, message, context, error }).subscribe(); + } + }; +}; + +// Register +provideLogging(withSinkFn(remoteSink)) +``` + +## Common Imports + +```typescript +// Main imports +import { logger, provideLoggerContext } from '@isa/core/logging'; + +// Configuration imports +import { + provideLogging, + withLogLevel, + withSink, + withContext, + LogLevel, + ConsoleLogSink +} from '@isa/core/logging'; + +// Type imports +import { + LoggerApi, + Sink, + SinkFn, + LoggerContext +} from '@isa/core/logging'; +``` diff --git a/.claude/skills/logging/examples.md b/.claude/skills/logging/references/examples.md similarity index 96% rename from .claude/skills/logging/examples.md rename to .claude/skills/logging/references/examples.md index 90e6d114c..5f19d9450 100644 --- a/.claude/skills/logging/examples.md +++ b/.claude/skills/logging/references/examples.md @@ -1,350 +1,350 @@ -# Logging Examples - -Concise real-world examples of logging patterns. - -## 1. Component with Observable - -```typescript -import { Component, OnInit } from '@angular/core'; -import { logger } from '@isa/core/logging'; - -@Component({ - selector: 'app-product-list', - standalone: true, -}) -export class ProductListComponent implements OnInit { - #logger = logger({ component: 'ProductListComponent' }); - - constructor(private productService: ProductService) {} - - ngOnInit(): void { - this.#logger.info('Component initialized'); - this.loadProducts(); - } - - private loadProducts(): void { - this.productService.getProducts().subscribe({ - next: (products) => { - this.#logger.info('Products loaded', () => ({ count: products.length })); - }, - error: (error) => { - this.#logger.error('Failed to load products', error); - } - }); - } -} -``` - -## 2. Service with HTTP - -```typescript -import { Injectable, inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { logger } from '@isa/core/logging'; -import { catchError, tap } from 'rxjs/operators'; - -@Injectable({ providedIn: 'root' }) -export class OrderService { - private http = inject(HttpClient); - #logger = logger({ service: 'OrderService' }); - - getOrder(id: string): Observable { - this.#logger.debug('Fetching order', { id }); - - return this.http.get(`/api/orders/${id}`).pipe( - tap((order) => this.#logger.info('Order fetched', () => ({ - id, - status: order.status - }))), - catchError((error) => { - this.#logger.error('Fetch failed', error, () => ({ id, status: error.status })); - throw error; - }) - ); - } -} -``` - -## 3. Hierarchical Context - -```typescript -import { Component } from '@angular/core'; -import { logger, provideLoggerContext } from '@isa/core/logging'; - -@Component({ - selector: 'oms-return-process', - standalone: true, - providers: [ - provideLoggerContext({ feature: 'returns', module: 'oms' }) - ], -}) -export class ReturnProcessComponent { - #logger = logger(() => ({ - processId: this.currentProcessId, - step: this.currentStep - })); - - private currentProcessId = crypto.randomUUID(); - private currentStep = 1; - - startProcess(orderId: string): void { - // Logs include: feature, module, processId, step, orderId - this.#logger.info('Process started', { orderId }); - } -} -``` - -## 4. NgRx Effect - -```typescript -import { Injectable } from '@angular/core'; -import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { logger } from '@isa/core/logging'; -import { map, catchError, tap } from 'rxjs/operators'; -import { of } from 'rxjs'; - -@Injectable() -export class OrdersEffects { - #logger = logger({ effect: 'OrdersEffects' }); - - loadOrders$ = createEffect(() => - this.actions$.pipe( - ofType(OrdersActions.loadOrders), - tap((action) => this.#logger.debug('Loading orders', () => ({ - page: action.page - }))), - mergeMap((action) => - this.orderService.getOrders(action.filters).pipe( - map((orders) => { - this.#logger.info('Orders loaded', () => ({ count: orders.length })); - return OrdersActions.loadOrdersSuccess({ orders }); - }), - catchError((error) => { - this.#logger.error('Load failed', error); - return of(OrdersActions.loadOrdersFailure({ error })); - }) - ) - ) - ) - ); - - constructor( - private actions$: Actions, - private orderService: OrderService - ) {} -} -``` - -## 5. Guard with Authorization - -```typescript -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; -import { logger } from '@isa/core/logging'; - -export const authGuard: CanActivateFn = (route, state) => { - const authService = inject(AuthService); - const router = inject(Router); - const log = logger({ guard: 'AuthGuard' }); - - if (authService.isAuthenticated()) { - log.debug('Access granted', () => ({ route: state.url })); - return true; - } - - log.warn('Access denied', () => ({ - attemptedRoute: state.url, - redirectTo: '/login' - })); - return router.createUrlTree(['/login']); -}; -``` - -## 6. HTTP Interceptor - -```typescript -import { HttpInterceptorFn } from '@angular/common/http'; -import { inject } from '@angular/core'; -import { tap, catchError } from 'rxjs/operators'; -import { LoggingService } from '@isa/core/logging'; - -export const loggingInterceptor: HttpInterceptorFn = (req, next) => { - const loggingService = inject(LoggingService); - const startTime = performance.now(); - - loggingService.debug('HTTP Request', () => ({ - method: req.method, - url: req.url - })); - - return next(req).pipe( - tap((event) => { - if (event.type === HttpEventType.Response) { - loggingService.info('HTTP Response', () => ({ - method: req.method, - url: req.url, - status: event.status, - duration: `${(performance.now() - startTime).toFixed(2)}ms` - })); - } - }), - catchError((error) => { - loggingService.error('HTTP Error', error, () => ({ - method: req.method, - url: req.url, - status: error.status - })); - return throwError(() => error); - }) - ); -}; -``` - -## 7. Form Validation - -```typescript -import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { logger } from '@isa/core/logging'; - -@Component({ - selector: 'shared-user-form', - standalone: true, -}) -export class UserFormComponent implements OnInit { - #logger = logger({ component: 'UserFormComponent' }); - form!: FormGroup; - - constructor(private fb: FormBuilder) {} - - ngOnInit(): void { - this.form = this.fb.group({ - name: ['', Validators.required], - email: ['', [Validators.required, Validators.email]] - }); - } - - onSubmit(): void { - if (this.form.invalid) { - this.#logger.warn('Invalid form submission', () => ({ - errors: this.getFormErrors() - })); - return; - } - - this.#logger.info('Form submitted'); - } - - private getFormErrors(): Record { - const errors: Record = {}; - Object.keys(this.form.controls).forEach((key) => { - const control = this.form.get(key); - if (control?.errors) errors[key] = control.errors; - }); - return errors; - } -} -``` - -## 8. Async Progress Tracking - -```typescript -import { Injectable } from '@angular/core'; -import { logger } from '@isa/core/logging'; -import { Observable } from 'rxjs'; -import { tap } from 'rxjs/operators'; - -@Injectable({ providedIn: 'root' }) -export class ImportService { - #logger = logger({ service: 'ImportService' }); - - importData(file: File): Observable { - const importId = crypto.randomUUID(); - - this.#logger.info('Import started', () => ({ - importId, - fileName: file.name, - fileSize: file.size - })); - - return this.processImport(file).pipe( - tap((progress) => { - if (progress % 25 === 0) { - this.#logger.debug('Import progress', () => ({ - importId, - progress: `${progress}%` - })); - } - }), - tap({ - complete: () => this.#logger.info('Import completed', { importId }), - error: (error) => this.#logger.error('Import failed', error, { importId }) - }) - ); - } - - private processImport(file: File): Observable { - // Implementation - } -} -``` - -## 9. Global Error Handler - -```typescript -import { Injectable, ErrorHandler } from '@angular/core'; -import { logger } from '@isa/core/logging'; - -@Injectable() -export class GlobalErrorHandler implements ErrorHandler { - #logger = logger({ handler: 'GlobalErrorHandler' }); - - handleError(error: Error): void { - this.#logger.error('Uncaught error', error, () => ({ - url: window.location.href, - userAgent: navigator.userAgent, - timestamp: new Date().toISOString() - })); - } -} -``` - -## 10. WebSocket Component - -```typescript -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { logger } from '@isa/core/logging'; -import { Subject, takeUntil } from 'rxjs'; - -@Component({ - selector: 'oms-live-orders', - standalone: true, -}) -export class LiveOrdersComponent implements OnInit, OnDestroy { - #logger = logger({ component: 'LiveOrdersComponent' }); - private destroy$ = new Subject(); - - constructor(private wsService: WebSocketService) {} - - ngOnInit(): void { - this.#logger.info('Connecting to WebSocket'); - - this.wsService.connect('orders').pipe( - takeUntil(this.destroy$) - ).subscribe({ - next: (msg) => this.#logger.debug('Message received', () => ({ - type: msg.type, - orderId: msg.orderId - })), - error: (error) => this.#logger.error('WebSocket error', error), - complete: () => this.#logger.info('WebSocket closed') - }); - } - - ngOnDestroy(): void { - this.#logger.debug('Component destroyed'); - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` +# Logging Examples + +Concise real-world examples of logging patterns. + +## 1. Component with Observable + +```typescript +import { Component, OnInit } from '@angular/core'; +import { logger } from '@isa/core/logging'; + +@Component({ + selector: 'app-product-list', + standalone: true, +}) +export class ProductListComponent implements OnInit { + #logger = logger({ component: 'ProductListComponent' }); + + constructor(private productService: ProductService) {} + + ngOnInit(): void { + this.#logger.info('Component initialized'); + this.loadProducts(); + } + + private loadProducts(): void { + this.productService.getProducts().subscribe({ + next: (products) => { + this.#logger.info('Products loaded', () => ({ count: products.length })); + }, + error: (error) => { + this.#logger.error('Failed to load products', error); + } + }); + } +} +``` + +## 2. Service with HTTP + +```typescript +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { logger } from '@isa/core/logging'; +import { catchError, tap } from 'rxjs/operators'; + +@Injectable({ providedIn: 'root' }) +export class OrderService { + private http = inject(HttpClient); + #logger = logger({ service: 'OrderService' }); + + getOrder(id: string): Observable { + this.#logger.debug('Fetching order', { id }); + + return this.http.get(`/api/orders/${id}`).pipe( + tap((order) => this.#logger.info('Order fetched', () => ({ + id, + status: order.status + }))), + catchError((error) => { + this.#logger.error('Fetch failed', error, () => ({ id, status: error.status })); + throw error; + }) + ); + } +} +``` + +## 3. Hierarchical Context + +```typescript +import { Component } from '@angular/core'; +import { logger, provideLoggerContext } from '@isa/core/logging'; + +@Component({ + selector: 'oms-return-process', + standalone: true, + providers: [ + provideLoggerContext({ feature: 'returns', module: 'oms' }) + ], +}) +export class ReturnProcessComponent { + #logger = logger(() => ({ + processId: this.currentProcessId, + step: this.currentStep + })); + + private currentProcessId = crypto.randomUUID(); + private currentStep = 1; + + startProcess(orderId: string): void { + // Logs include: feature, module, processId, step, orderId + this.#logger.info('Process started', { orderId }); + } +} +``` + +## 4. NgRx Effect + +```typescript +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { logger } from '@isa/core/logging'; +import { map, catchError, tap } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Injectable() +export class OrdersEffects { + #logger = logger({ effect: 'OrdersEffects' }); + + loadOrders$ = createEffect(() => + this.actions$.pipe( + ofType(OrdersActions.loadOrders), + tap((action) => this.#logger.debug('Loading orders', () => ({ + page: action.page + }))), + mergeMap((action) => + this.orderService.getOrders(action.filters).pipe( + map((orders) => { + this.#logger.info('Orders loaded', () => ({ count: orders.length })); + return OrdersActions.loadOrdersSuccess({ orders }); + }), + catchError((error) => { + this.#logger.error('Load failed', error); + return of(OrdersActions.loadOrdersFailure({ error })); + }) + ) + ) + ) + ); + + constructor( + private actions$: Actions, + private orderService: OrderService + ) {} +} +``` + +## 5. Guard with Authorization + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { logger } from '@isa/core/logging'; + +export const authGuard: CanActivateFn = (route, state) => { + const authService = inject(AuthService); + const router = inject(Router); + const log = logger({ guard: 'AuthGuard' }); + + if (authService.isAuthenticated()) { + log.debug('Access granted', () => ({ route: state.url })); + return true; + } + + log.warn('Access denied', () => ({ + attemptedRoute: state.url, + redirectTo: '/login' + })); + return router.createUrlTree(['/login']); +}; +``` + +## 6. HTTP Interceptor + +```typescript +import { HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { tap, catchError } from 'rxjs/operators'; +import { LoggingService } from '@isa/core/logging'; + +export const loggingInterceptor: HttpInterceptorFn = (req, next) => { + const loggingService = inject(LoggingService); + const startTime = performance.now(); + + loggingService.debug('HTTP Request', () => ({ + method: req.method, + url: req.url + })); + + return next(req).pipe( + tap((event) => { + if (event.type === HttpEventType.Response) { + loggingService.info('HTTP Response', () => ({ + method: req.method, + url: req.url, + status: event.status, + duration: `${(performance.now() - startTime).toFixed(2)}ms` + })); + } + }), + catchError((error) => { + loggingService.error('HTTP Error', error, () => ({ + method: req.method, + url: req.url, + status: error.status + })); + return throwError(() => error); + }) + ); +}; +``` + +## 7. Form Validation + +```typescript +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { logger } from '@isa/core/logging'; + +@Component({ + selector: 'shared-user-form', + standalone: true, +}) +export class UserFormComponent implements OnInit { + #logger = logger({ component: 'UserFormComponent' }); + form!: FormGroup; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.form = this.fb.group({ + name: ['', Validators.required], + email: ['', [Validators.required, Validators.email]] + }); + } + + onSubmit(): void { + if (this.form.invalid) { + this.#logger.warn('Invalid form submission', () => ({ + errors: this.getFormErrors() + })); + return; + } + + this.#logger.info('Form submitted'); + } + + private getFormErrors(): Record { + const errors: Record = {}; + Object.keys(this.form.controls).forEach((key) => { + const control = this.form.get(key); + if (control?.errors) errors[key] = control.errors; + }); + return errors; + } +} +``` + +## 8. Async Progress Tracking + +```typescript +import { Injectable } from '@angular/core'; +import { logger } from '@isa/core/logging'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Injectable({ providedIn: 'root' }) +export class ImportService { + #logger = logger({ service: 'ImportService' }); + + importData(file: File): Observable { + const importId = crypto.randomUUID(); + + this.#logger.info('Import started', () => ({ + importId, + fileName: file.name, + fileSize: file.size + })); + + return this.processImport(file).pipe( + tap((progress) => { + if (progress % 25 === 0) { + this.#logger.debug('Import progress', () => ({ + importId, + progress: `${progress}%` + })); + } + }), + tap({ + complete: () => this.#logger.info('Import completed', { importId }), + error: (error) => this.#logger.error('Import failed', error, { importId }) + }) + ); + } + + private processImport(file: File): Observable { + // Implementation + } +} +``` + +## 9. Global Error Handler + +```typescript +import { Injectable, ErrorHandler } from '@angular/core'; +import { logger } from '@isa/core/logging'; + +@Injectable() +export class GlobalErrorHandler implements ErrorHandler { + #logger = logger({ handler: 'GlobalErrorHandler' }); + + handleError(error: Error): void { + this.#logger.error('Uncaught error', error, () => ({ + url: window.location.href, + userAgent: navigator.userAgent, + timestamp: new Date().toISOString() + })); + } +} +``` + +## 10. WebSocket Component + +```typescript +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { logger } from '@isa/core/logging'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'oms-live-orders', + standalone: true, +}) +export class LiveOrdersComponent implements OnInit, OnDestroy { + #logger = logger({ component: 'LiveOrdersComponent' }); + private destroy$ = new Subject(); + + constructor(private wsService: WebSocketService) {} + + ngOnInit(): void { + this.#logger.info('Connecting to WebSocket'); + + this.wsService.connect('orders').pipe( + takeUntil(this.destroy$) + ).subscribe({ + next: (msg) => this.#logger.debug('Message received', () => ({ + type: msg.type, + orderId: msg.orderId + })), + error: (error) => this.#logger.error('WebSocket error', error), + complete: () => this.#logger.info('WebSocket closed') + }); + } + + ngOnDestroy(): void { + this.#logger.debug('Component destroyed'); + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` diff --git a/.claude/skills/logging/troubleshooting.md b/.claude/skills/logging/references/troubleshooting.md similarity index 95% rename from .claude/skills/logging/troubleshooting.md rename to .claude/skills/logging/references/troubleshooting.md index 1df623b13..ed9612cc3 100644 --- a/.claude/skills/logging/troubleshooting.md +++ b/.claude/skills/logging/references/troubleshooting.md @@ -1,235 +1,235 @@ -# Logging Troubleshooting - -## 1. Logs Not Appearing - -**Problem:** Logger called but nothing in console. - -**Solutions:** -```typescript -// Check log level -provideLogging( - withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) -) - -// Add sink -provideLogging( - withLogLevel(LogLevel.Debug), - withSink(ConsoleLogSink) // Required! -) - -// Verify configuration in app.config.ts -export const appConfig: ApplicationConfig = { - providers: [ - provideLogging(...) // Must be present - ] -}; -``` - -## 2. NullInjectorError - -**Error:** `NullInjectorError: No provider for LoggingService!` - -**Solution:** -```typescript -// app.config.ts -import { provideLogging, withLogLevel, withSink, - LogLevel, ConsoleLogSink } from '@isa/core/logging'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideLogging( - withLogLevel(LogLevel.Debug), - withSink(ConsoleLogSink) - ) - ] -}; -``` - -## 3. Context Not Showing - -**Problem:** Context passed but doesn't appear. - -**Check:** -```typescript -// βœ… Both work: -this.#logger.info('Message', () => ({ id: '123' })); // Function -this.#logger.info('Message', { id: '123' }); // Object - -// ❌ Common mistake: -const ctx = { id: '123' }; -this.#logger.info('Message', ctx); // Actually works! - -// Verify hierarchical merge: -// Global β†’ Component β†’ Instance β†’ Message -``` - -## 4. Performance Issues - -**Problem:** Slow when debug logging enabled. - -**Solutions:** -```typescript -// βœ… Use lazy evaluation -this.#logger.debug('Data', () => ({ - expensive: this.compute() // Only if debug enabled -})); - -// βœ… Reduce log frequency -this.#logger.debug('Batch', () => ({ - count: items.length // Not each item -})); - -// βœ… Increase production level -provideLogging( - withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) -) -``` - -## 5. Error Object Not Logged - -**Problem:** Error shows as `[object Object]`. - -**Solution:** -```typescript -// ❌ Wrong -this.#logger.error('Failed', { error }); // Don't wrap in object - -// βœ… Correct -this.#logger.error('Failed', error as Error, () => ({ - additionalContext: 'value' -})); -``` - -## 6. TypeScript Errors - -**Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'` - -**Solution:** -```typescript -// ❌ Wrong type -this.#logger.info('Message', 'string'); // Invalid - -// βœ… Correct types -this.#logger.info('Message', { key: 'value' }); -this.#logger.info('Message', () => ({ key: 'value' })); -``` - -## 7. Logs in Tests - -**Problem:** Test output cluttered with logs. - -**Solutions:** -```typescript -// Mock logging service -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { LoggingService } from '@isa/core/logging'; - -const createComponent = createComponentFactory({ - component: MyComponent, - mocks: [LoggingService] // Mocks all log methods -}); - -// Or disable in tests -TestBed.configureTestingModule({ - providers: [ - provideLogging(withLogLevel(LogLevel.Off)) - ] -}); -``` - -## 8. Undefined Property Error - -**Error:** `Cannot read property 'X' of undefined` - -**Problem:** Accessing uninitialized property in logger context. - -**Solutions:** -```typescript -// ❌ Problem -#logger = logger(() => ({ - userId: this.userService.currentUserId // May be undefined -})); - -// βœ… Solution 1: Optional chaining -#logger = logger(() => ({ - userId: this.userService?.currentUserId ?? 'unknown' -})); - -// βœ… Solution 2: Delay access -ngOnInit() { - this.#logger.info('Init', () => ({ - userId: this.userService.currentUserId // Safe here - })); -} -``` - -## 9. Circular Dependency - -**Error:** `NG0200: Circular dependency in DI detected` - -**Cause:** Service A ← β†’ Service B both inject LoggingService. - -**Solution:** -```typescript -// ❌ Creates circular dependency -constructor(private loggingService: LoggingService) {} - -// βœ… Use factory (no circular dependency) -#logger = logger({ service: 'MyService' }); -``` - -## 10. Custom Sink Not Working - -**Problem:** Sink registered but never called. - -**Solutions:** -```typescript -// βœ… Correct registration -provideLogging( - withSink(MySink) // Add to config -) - -// βœ… Correct signature -export class MySink implements Sink { - log( - level: LogLevel, - message: string, - context?: LoggerContext, - error?: Error - ): void { - // Implementation - } -} - -// βœ… Sink function must return function -export const mySinkFn: SinkFn = () => { - const http = inject(HttpClient); - return (level, message, context, error) => { - // Implementation - }; -}; -``` - -## Quick Diagnostics - -```typescript -// Enable all logs temporarily -provideLogging(withLogLevel(LogLevel.Trace)) - -// Check imports -import { logger } from '@isa/core/logging'; // βœ… Correct -import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong - -// Verify console filters in browser DevTools -// Ensure Info, Debug, Warnings are enabled -``` - -## Common Error Messages - -| Error | Cause | Fix | -|-------|-------|-----| -| `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` | -| `Type 'X' not assignable` | Wrong context type | Use object or function | -| `Cannot read property 'X'` | Undefined property | Use optional chaining | -| `Circular dependency` | Service injection | Use `logger()` factory | -| Stack overflow | Infinite loop in context | Don't call logger in context | +# Logging Troubleshooting + +## 1. Logs Not Appearing + +**Problem:** Logger called but nothing in console. + +**Solutions:** +```typescript +// Check log level +provideLogging( + withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) +) + +// Add sink +provideLogging( + withLogLevel(LogLevel.Debug), + withSink(ConsoleLogSink) // Required! +) + +// Verify configuration in app.config.ts +export const appConfig: ApplicationConfig = { + providers: [ + provideLogging(...) // Must be present + ] +}; +``` + +## 2. NullInjectorError + +**Error:** `NullInjectorError: No provider for LoggingService!` + +**Solution:** +```typescript +// app.config.ts +import { provideLogging, withLogLevel, withSink, + LogLevel, ConsoleLogSink } from '@isa/core/logging'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideLogging( + withLogLevel(LogLevel.Debug), + withSink(ConsoleLogSink) + ) + ] +}; +``` + +## 3. Context Not Showing + +**Problem:** Context passed but doesn't appear. + +**Check:** +```typescript +// βœ… Both work: +this.#logger.info('Message', () => ({ id: '123' })); // Function +this.#logger.info('Message', { id: '123' }); // Object + +// ❌ Common mistake: +const ctx = { id: '123' }; +this.#logger.info('Message', ctx); // Actually works! + +// Verify hierarchical merge: +// Global β†’ Component β†’ Instance β†’ Message +``` + +## 4. Performance Issues + +**Problem:** Slow when debug logging enabled. + +**Solutions:** +```typescript +// βœ… Use lazy evaluation +this.#logger.debug('Data', () => ({ + expensive: this.compute() // Only if debug enabled +})); + +// βœ… Reduce log frequency +this.#logger.debug('Batch', () => ({ + count: items.length // Not each item +})); + +// βœ… Increase production level +provideLogging( + withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) +) +``` + +## 5. Error Object Not Logged + +**Problem:** Error shows as `[object Object]`. + +**Solution:** +```typescript +// ❌ Wrong +this.#logger.error('Failed', { error }); // Don't wrap in object + +// βœ… Correct +this.#logger.error('Failed', error as Error, () => ({ + additionalContext: 'value' +})); +``` + +## 6. TypeScript Errors + +**Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'` + +**Solution:** +```typescript +// ❌ Wrong type +this.#logger.info('Message', 'string'); // Invalid + +// βœ… Correct types +this.#logger.info('Message', { key: 'value' }); +this.#logger.info('Message', () => ({ key: 'value' })); +``` + +## 7. Logs in Tests + +**Problem:** Test output cluttered with logs. + +**Solutions:** +```typescript +// Mock logging service +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { LoggingService } from '@isa/core/logging'; + +const createComponent = createComponentFactory({ + component: MyComponent, + mocks: [LoggingService] // Mocks all log methods +}); + +// Or disable in tests +TestBed.configureTestingModule({ + providers: [ + provideLogging(withLogLevel(LogLevel.Off)) + ] +}); +``` + +## 8. Undefined Property Error + +**Error:** `Cannot read property 'X' of undefined` + +**Problem:** Accessing uninitialized property in logger context. + +**Solutions:** +```typescript +// ❌ Problem +#logger = logger(() => ({ + userId: this.userService.currentUserId // May be undefined +})); + +// βœ… Solution 1: Optional chaining +#logger = logger(() => ({ + userId: this.userService?.currentUserId ?? 'unknown' +})); + +// βœ… Solution 2: Delay access +ngOnInit() { + this.#logger.info('Init', () => ({ + userId: this.userService.currentUserId // Safe here + })); +} +``` + +## 9. Circular Dependency + +**Error:** `NG0200: Circular dependency in DI detected` + +**Cause:** Service A ← β†’ Service B both inject LoggingService. + +**Solution:** +```typescript +// ❌ Creates circular dependency +constructor(private loggingService: LoggingService) {} + +// βœ… Use factory (no circular dependency) +#logger = logger({ service: 'MyService' }); +``` + +## 10. Custom Sink Not Working + +**Problem:** Sink registered but never called. + +**Solutions:** +```typescript +// βœ… Correct registration +provideLogging( + withSink(MySink) // Add to config +) + +// βœ… Correct signature +export class MySink implements Sink { + log( + level: LogLevel, + message: string, + context?: LoggerContext, + error?: Error + ): void { + // Implementation + } +} + +// βœ… Sink function must return function +export const mySinkFn: SinkFn = () => { + const http = inject(HttpClient); + return (level, message, context, error) => { + // Implementation + }; +}; +``` + +## Quick Diagnostics + +```typescript +// Enable all logs temporarily +provideLogging(withLogLevel(LogLevel.Trace)) + +// Check imports +import { logger } from '@isa/core/logging'; // βœ… Correct +import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong + +// Verify console filters in browser DevTools +// Ensure Info, Debug, Warnings are enabled +``` + +## Common Error Messages + +| Error | Cause | Fix | +|-------|-------|-----| +| `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` | +| `Type 'X' not assignable` | Wrong context type | Use object or function | +| `Cannot read property 'X'` | Undefined property | Use optional chaining | +| `Circular dependency` | Service injection | Use `logger()` factory | +| Stack overflow | Infinite loop in context | Don't call logger in context | diff --git a/.claude/skills/tailwind/SKILL.md b/.claude/skills/tailwind/SKILL.md index 53ec3206a..8c302677e 100644 --- a/.claude/skills/tailwind/SKILL.md +++ b/.claude/skills/tailwind/SKILL.md @@ -9,29 +9,6 @@ description: This skill should be used when working with Tailwind CSS styling in Assist with applying the ISA-specific Tailwind CSS design system throughout the ISA-Frontend Angular monorepo. This skill provides comprehensive knowledge of custom utilities, color palettes, typography classes, button variants, and layout patterns specific to this project. -## When to Use This Skill - -Invoke this skill when: -- **After** checking `libs/ui/**` for existing components (always check first!) -- Styling layout and spacing for components -- Choosing appropriate color values for custom elements -- Applying typography classes to text content -- Determining spacing, layout, or responsive breakpoints -- Customizing or extending existing UI components -- Ensuring design system consistency -- Questions about which Tailwind utility classes are available - -**Important**: This skill provides Tailwind utilities. Always prefer using components from `@isa/ui/*` libraries before applying custom Tailwind styles. - -**Works together with:** -- **[template-standards](../template-standards/SKILL.md)** - Angular template syntax, E2E testing attributes, and ARIA accessibility -- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files - -When building Angular components, these skills work together: -1. Use **template-standards** for Angular syntax, `data-*`, and ARIA attributes -2. Use **tailwind** (this skill) for styling with the ISA design system -3. Use **logging** for all component/service logging - ## Core Design System Principles ### 0. Component Libraries First (Most Important) diff --git a/.claude/skills/template-standards/SKILL.md b/.claude/skills/template-standards/SKILL.md index 2309e2b2e..61bb2492f 100644 --- a/.claude/skills/template-standards/SKILL.md +++ b/.claude/skills/template-standards/SKILL.md @@ -7,20 +7,6 @@ description: This skill should be used when writing or reviewing Angular compone Comprehensive guide for Angular templates covering modern syntax, E2E testing attributes, and ARIA accessibility. -## When to Use - -- Creating or reviewing component templates -- Refactoring legacy `*ngIf/*ngFor/*ngSwitch` to modern syntax -- Implementing `@defer` lazy loading -- Designing reusable components with `ng-content` -- Adding E2E testing attributes for automated tests -- Ensuring WCAG accessibility compliance -- Template performance optimization - -**Related Skills:** -- **[tailwind](../tailwind/SKILL.md)** - ISA design system styling (colors, typography, spacing, layout) -- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files using `@isa/core/logging` - ## Overview This skill combines three essential aspects of Angular template development: @@ -31,7 +17,9 @@ This skill combines three essential aspects of Angular template development: **Every interactive element MUST include both E2E and ARIA attributes.** ---- +**Related Skills:** +- **tailwind** - ISA design system styling (colors, typography, spacing, layout) +- **logging** - MANDATORY logging in all Angular files using `@isa/core/logging` ## Part 1: Angular Template Syntax @@ -81,6 +69,8 @@ This skill combines three essential aspects of Angular template development: } ``` +**See [control-flow-reference.md](references/control-flow-reference.md) for advanced patterns including nested loops, complex conditions, and filter strategies.** + ### @defer Lazy Loading #### Basic Usage @@ -97,7 +87,7 @@ This skill combines three essential aspects of Angular template development: } ``` -#### Triggers +#### Common Triggers | Trigger | Use Case | |---------|----------| @@ -111,18 +101,15 @@ This skill combines three essential aspects of Angular template development: **Multiple triggers:** `@defer (on interaction; on timer(5s))` **Prefetching:** `@defer (on interaction; prefetch on idle)` -#### Requirements +#### Critical Requirements - Components **MUST be standalone** - No `@ViewChild`/`@ContentChild` references - Reserve space in `@placeholder` to prevent layout shift +- Never defer above-the-fold content (harms LCP) +- Avoid `immediate`/`timer` during initial render (harms TTI) -#### Best Practices - -- βœ… Defer below-the-fold content -- ❌ Never defer above-the-fold (harms LCP) -- ❌ Avoid `immediate`/`timer` during initial render (harms TTI) -- Test with network throttling +**See [defer-patterns.md](references/defer-patterns.md) for performance optimization, Core Web Vitals impact, bundle size reduction strategies, and real-world examples.** ### Content Projection @@ -156,12 +143,9 @@ This skill combines three essential aspects of Angular template development: ``` -**Fallback content:** `Default Title` -**Aliasing:** `

Title

` +**CRITICAL Constraint:** `ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`. -#### CRITICAL Constraint - -`ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`. +**See [projection-patterns.md](references/projection-patterns.md) for conditional projection, template-based projection, querying projected content, and modal/form field examples.** ### Template References @@ -193,6 +177,8 @@ Groups elements without DOM footprint:

``` +**See [template-reference.md](references/template-reference.md) for programmatic rendering, ViewContainerRef patterns, context scoping, and common pitfalls.** + ### Variables #### @let (Angular 18.1+) @@ -226,16 +212,14 @@ Groups elements without DOM footprint: **Class:** `[class.active]="isActive()"` or `[class]="{active: isActive()}"` **Style:** `[style.width.px]="width()"` or `[style]="{color: textColor()}"` ---- - ## Part 2: E2E Testing Attributes ### Purpose Enable automated end-to-end testing by providing stable selectors for QA automation: -- **`data-what`**: Semantic description of element's purpose -- **`data-which`**: Unique identifier for specific instances -- **`data-*`**: Additional contextual information +- **`data-what`**: Semantic description of element's purpose (e.g., `submit-button`, `email-input`) +- **`data-which`**: Unique identifier for specific instances (e.g., `registration-form`, `customer-123`) +- **`data-*`**: Additional contextual information (e.g., `data-status="active"`) ### Naming Conventions @@ -259,9 +243,8 @@ Enable automated end-to-end testing by providing stable selectors for QA automat 3. βœ… Ensure `data-which` is unique within the view 4. βœ… Use Angular binding for dynamic values: `[attr.data-*]` 5. βœ… Avoid including sensitive data in attributes -6. βœ… Document complex attribute patterns in template comments ---- +**See [e2e-attributes.md](references/e2e-attributes.md) for complete patterns by element type (buttons, inputs, links, lists, tables, dialogs), dynamic attribute bindings, testing integration examples, and validation strategies.** ## Part 3: ARIA Accessibility Attributes @@ -290,7 +273,7 @@ Ensure web applications are accessible to all users, including those using assis 6. βœ… Keep ARIA attributes in sync with visual states 7. βœ… Test with screen readers (NVDA, JAWS, VoiceOver) ---- +**See [aria-attributes.md](references/aria-attributes.md) for comprehensive role reference, property and state attributes, live regions, keyboard navigation patterns, WCAG compliance requirements, and testing strategies.** ## Part 4: Combined Examples @@ -342,18 +325,6 @@ Ensure web applications are accessible to all users, including those using assis } ``` -### Link with All Attributes - -```html - - View Order #{{ orderNumber }} - -``` - ### Dialog with All Attributes ```html @@ -387,109 +358,7 @@ Ensure web applications are accessible to all users, including those using assis
``` -### Complete Form Example - -```html -
- -

User Registration

- -
- - - Must be at least 3 characters -
- -
- - - @if (emailError) { - - {{ emailError }} - - } -
- -
- - - -
-
-``` - -### Conditional Rendering with Attributes - -```typescript -@if (isLoading()) { -
- -
-} @else if (error()) { -
- {{ error() }} -
-} @else { -
-

{{ user().name }}

- -
-} -``` - ---- +**See [combined-patterns.md](references/combined-patterns.md) for complete form examples, product listings, shopping carts, modal dialogs, navigation patterns, data tables, search interfaces, notifications, and multi-step forms with all attributes properly applied.** ## Validation Checklist @@ -501,7 +370,6 @@ Before considering template complete: - [ ] Complex expressions moved to `computed()` in component - [ ] @defer used for below-the-fold or heavy components - [ ] Content projection using ng-content with proper selectors (if applicable) -- [ ] Template references using # for local variables - [ ] Using @let for template-scoped variables (Angular 18.1+) ### E2E Attributes @@ -510,7 +378,6 @@ Before considering template complete: - [ ] All links have `data-what` and `data-which` - [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers - [ ] No duplicate `data-which` values within the same view -- [ ] Additional `data-*` attributes for contextual information (if needed) ### ARIA Accessibility - [ ] All interactive elements have appropriate ARIA labels @@ -520,16 +387,13 @@ Before considering template complete: - [ ] Dialogs have `role="dialog"`, `aria-modal`, and label relationships - [ ] Dynamic state changes reflected in ARIA attributes - [ ] Keyboard accessibility (tabindex, enter/space handlers where needed) -- [ ] Screen reader testing completed (if applicable) ### Combined Standards - [ ] Every interactive element has BOTH E2E and ARIA attributes -- [ ] Attributes organized logically in template (Angular directives → data-* → aria-*) +- [ ] Attributes organized logically (Angular directives → data-* → aria-*) - [ ] Dynamic bindings use `[attr.*]` syntax correctly - [ ] No accessibility violations (semantic HTML preferred over ARIA) ---- - ## Migration Guide ### From Legacy Angular Syntax @@ -549,31 +413,27 @@ Before considering template complete: 3. **Add ARIA attributes**: appropriate role, label, and state attributes 4. **Test**: verify selectors work in E2E tests, validate with screen readers ---- - ## Reference Files For detailed examples and advanced patterns, see: ### Angular Syntax References -- `references/control-flow-reference.md` - @if/@for/@switch patterns -- `references/defer-patterns.md` - Lazy loading strategies -- `references/projection-patterns.md` - Advanced ng-content -- `references/template-reference.md` - ng-template/ng-container +- `references/control-flow-reference.md` - Advanced @if/@for/@switch patterns, nested loops, filtering +- `references/defer-patterns.md` - Lazy loading strategies, Core Web Vitals, performance optimization +- `references/projection-patterns.md` - Advanced ng-content, conditional projection, template-based patterns +- `references/template-reference.md` - ng-template/ng-container, programmatic rendering, ViewContainerRef ### E2E Testing References -- `references/e2e-attributes.md` - Complete E2E attribute patterns and conventions +- `references/e2e-attributes.md` - Complete E2E attribute patterns, naming conventions, testing integration ### ARIA Accessibility References -- `references/aria-attributes.md` - Comprehensive ARIA guidance and WCAG compliance +- `references/aria-attributes.md` - Comprehensive ARIA guidance, roles, properties, states, WCAG compliance ### Combined References -- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA +- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA integrated Search with: `grep -r "pattern" references/` ---- - ## Quick Reference Summary **Every interactive element needs:** diff --git a/.claude/skills/test-migration/SKILL.md b/.claude/skills/test-migration/SKILL.md index 8b28fbedf..7b9cdfa17 100644 --- a/.claude/skills/test-migration/SKILL.md +++ b/.claude/skills/test-migration/SKILL.md @@ -1,192 +1,192 @@ ---- -name: test-migration -description: Reference patterns for Jest to Vitest test migration. Contains syntax mappings for jest→vi, Spectator→Angular Testing Library, and common matcher conversions. Auto-loaded by migration-specialist agent. ---- - -# Jest to Vitest Migration Patterns - -Quick reference for test framework migration syntax. - -## Import Changes - -```typescript -// REMOVE -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; - -// ADD -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { describe, it, expect, beforeEach, vi } from 'vitest'; -``` - -## Mock Migration - -| Jest | Vitest | -|------|--------| -| `jest.fn()` | `vi.fn()` | -| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` | -| `jest.mock('module')` | `vi.mock('module')` | -| `jest.useFakeTimers()` | `vi.useFakeTimers()` | -| `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` | -| `jest.useRealTimers()` | `vi.useRealTimers()` | -| `jest.clearAllMocks()` | `vi.clearAllMocks()` | -| `jest.resetAllMocks()` | `vi.resetAllMocks()` | - -## Spectator → TestBed - -### Component Testing - -```typescript -// OLD (Spectator) -const createComponent = createComponentFactory({ - component: MyComponent, - imports: [CommonModule], - mocks: [MyService] -}); -let spectator: Spectator; -beforeEach(() => spectator = createComponent()); - -// NEW (TestBed) -let fixture: ComponentFixture; -let component: MyComponent; -beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MyComponent], - providers: [{ provide: MyService, useValue: mockService }] - }).compileComponents(); - fixture = TestBed.createComponent(MyComponent); - component = fixture.componentInstance; -}); -``` - -### Service Testing - -```typescript -// OLD (Spectator) -const createService = createServiceFactory({ - service: MyService, - mocks: [HttpClient] -}); -let spectator: SpectatorService; -beforeEach(() => spectator = createService()); - -// NEW (TestBed) -let service: MyService; -beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - MyService, - { provide: HttpClient, useValue: mockHttp } - ] - }); - service = TestBed.inject(MyService); -}); -``` - -## Query Selectors - -| Spectator | Angular/Native | -|-----------|---------------| -| `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` | -| `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` | -| `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` | - -## Events - -| Spectator | Native | -|-----------|--------| -| `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` | -| `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` | -| `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` | - -## Matchers - -| Spectator | Standard | -|-----------|----------| -| `toHaveText('text')` | `expect(el.textContent).toContain('text')` | -| `toExist()` | `toBeTruthy()` | -| `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` | -| `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` | - -## Async Patterns - -```typescript -// OLD (callback) -it('should emit', (done) => { - service.data$.subscribe(val => { - expect(val).toBe(expected); - done(); - }); -}); - -// NEW (async/await) -import { firstValueFrom } from 'rxjs'; -it('should emit', async () => { - const val = await firstValueFrom(service.data$); - expect(val).toBe(expected); -}); -``` - -## Input/Output Testing - -```typescript -// Setting inputs (Angular 17.3+) -fixture.componentRef.setInput('title', 'Test'); -fixture.detectChanges(); - -// Testing outputs -const emitSpy = vi.fn(); -component.myOutput.subscribe(emitSpy); -// trigger action... -expect(emitSpy).toHaveBeenCalledWith(expectedValue); -``` - -## Configuration Files - -### vite.config.mts Template - -```typescript -/// -import { defineConfig } from 'vite'; -import angular from '@analogjs/vite-plugin-angular'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; - -export default -// @ts-expect-error - Vitest reporter tuple types have complex inference issues -defineConfig(() => ({ - root: __dirname, - cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth! - plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - test: { - watch: false, - globals: true, - environment: 'jsdom', - include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['src/test-setup.ts'], - reporters: [ - 'default', - ['junit', { outputFile: '../../../testresults/junit-[name].xml' }], - ], - coverage: { - reportsDirectory: '../../../coverage/libs/[path]', - provider: 'v8' as const, - reporter: ['text', 'cobertura'], - }, - }, -})); -``` - -### test-setup.ts - -```typescript -import '@analogjs/vitest-angular/setup-zone'; -``` - -## Path Depth Reference - -| Library Location | Relative Path Prefix | -|-----------------|---------------------| -| `libs/feature/ui` | `../../` | -| `libs/feature/data-access` | `../../` | -| `libs/domain/feature/ui` | `../../../` | -| `libs/domain/feature/data-access/store` | `../../../../` | +--- +name: test-migration +description: Reference patterns for Jest to Vitest test migration. Contains syntax mappings for jest→vi, Spectator→Angular Testing Library, and common matcher conversions. Auto-loaded by migration-specialist agent. +--- + +# Jest to Vitest Migration Patterns + +Quick reference for test framework migration syntax. + +## Import Changes + +```typescript +// REMOVE +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; + +// ADD +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +``` + +## Mock Migration + +| Jest | Vitest | +|------|--------| +| `jest.fn()` | `vi.fn()` | +| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` | +| `jest.mock('module')` | `vi.mock('module')` | +| `jest.useFakeTimers()` | `vi.useFakeTimers()` | +| `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` | +| `jest.useRealTimers()` | `vi.useRealTimers()` | +| `jest.clearAllMocks()` | `vi.clearAllMocks()` | +| `jest.resetAllMocks()` | `vi.resetAllMocks()` | + +## Spectator → TestBed + +### Component Testing + +```typescript +// OLD (Spectator) +const createComponent = createComponentFactory({ + component: MyComponent, + imports: [CommonModule], + mocks: [MyService] +}); +let spectator: Spectator; +beforeEach(() => spectator = createComponent()); + +// NEW (TestBed) +let fixture: ComponentFixture; +let component: MyComponent; +beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MyComponent], + providers: [{ provide: MyService, useValue: mockService }] + }).compileComponents(); + fixture = TestBed.createComponent(MyComponent); + component = fixture.componentInstance; +}); +``` + +### Service Testing + +```typescript +// OLD (Spectator) +const createService = createServiceFactory({ + service: MyService, + mocks: [HttpClient] +}); +let spectator: SpectatorService; +beforeEach(() => spectator = createService()); + +// NEW (TestBed) +let service: MyService; +beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + MyService, + { provide: HttpClient, useValue: mockHttp } + ] + }); + service = TestBed.inject(MyService); +}); +``` + +## Query Selectors + +| Spectator | Angular/Native | +|-----------|---------------| +| `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` | +| `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` | +| `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` | + +## Events + +| Spectator | Native | +|-----------|--------| +| `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` | +| `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` | +| `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` | + +## Matchers + +| Spectator | Standard | +|-----------|----------| +| `toHaveText('text')` | `expect(el.textContent).toContain('text')` | +| `toExist()` | `toBeTruthy()` | +| `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` | +| `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` | + +## Async Patterns + +```typescript +// OLD (callback) +it('should emit', (done) => { + service.data$.subscribe(val => { + expect(val).toBe(expected); + done(); + }); +}); + +// NEW (async/await) +import { firstValueFrom } from 'rxjs'; +it('should emit', async () => { + const val = await firstValueFrom(service.data$); + expect(val).toBe(expected); +}); +``` + +## Input/Output Testing + +```typescript +// Setting inputs (Angular 17.3+) +fixture.componentRef.setInput('title', 'Test'); +fixture.detectChanges(); + +// Testing outputs +const emitSpy = vi.fn(); +component.myOutput.subscribe(emitSpy); +// trigger action... +expect(emitSpy).toHaveBeenCalledWith(expectedValue); +``` + +## Configuration Files + +### vite.config.mts Template + +```typescript +/// +import { defineConfig } from 'vite'; +import angular from '@analogjs/vite-plugin-angular'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default +// @ts-expect-error - Vitest reporter tuple types have complex inference issues +defineConfig(() => ({ + root: __dirname, + cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth! + plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: ['src/test-setup.ts'], + reporters: [ + 'default', + ['junit', { outputFile: '../../../testresults/junit-[name].xml' }], + ], + coverage: { + reportsDirectory: '../../../coverage/libs/[path]', + provider: 'v8' as const, + reporter: ['text', 'cobertura'], + }, + }, +})); +``` + +### test-setup.ts + +```typescript +import '@analogjs/vitest-angular/setup-zone'; +``` + +## Path Depth Reference + +| Library Location | Relative Path Prefix | +|-----------------|---------------------| +| `libs/feature/ui` | `../../` | +| `libs/feature/data-access` | `../../` | +| `libs/domain/feature/ui` | `../../../` | +| `libs/domain/feature/data-access/store` | `../../../../` |