♻️ refactor(claude): migrate skills to new skill-creator format

Recreate all 11 skills using the updated skill-creator tooling:
- api-sync, arch-docs, architecture-validator, css-animations
- git-workflow, library-creator, logging, state-patterns
- tailwind, template-standards, test-migration

Key changes:
- Updated YAML frontmatter structure
- Reorganized logging references into references/ subdirectory
- Applied progressive disclosure patterns where applicable
This commit is contained in:
Lorenz Hilpert
2025-12-11 12:27:15 +01:00
parent 7612394ba1
commit 43e4a6bf64
13 changed files with 2434 additions and 2666 deletions

View File

@@ -1,371 +1,362 @@
--- ---
name: api-sync 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. 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 # API Sync Manager
## Overview ## 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. 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 ## Available APIs
Invoke when user requests: availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api
- API client regeneration or sync
- "Check breaking changes" before API update ## Unified Sync Workflow
- Backend API changes need frontend updates
- Impact assessment of API changes ### Step 1: Pre-Generation Check
- "Regenerate swagger" or "update API clients"
```bash
## Available APIs # Check uncommitted changes
git status generated/swagger/
availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api
# Verify no manual edits will be lost
## Unified Sync Workflow git diff generated/swagger/
```
### Step 1: Pre-Generation Check
If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first.
```bash
# Check uncommitted changes ### Step 2: Pre-Generation Breaking Change Detection
git status generated/swagger/
Generate to temporary location to compare without affecting working directory:
# Verify no manual edits will be lost
git diff generated/swagger/ ```bash
``` # Backup current state
cp -r generated/swagger/[api-name] /tmp/[api-name].backup
If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first.
# Generate to temp location for comparison
### Step 2: Pre-Generation Breaking Change Detection npm run generate:swagger:[api-name]
```
Generate to temporary location to compare without affecting working directory:
**Compare Models and Services:**
```bash
# Backup current state ```bash
cp -r generated/swagger/[api-name] /tmp/[api-name].backup 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
# Generate to temp location for comparison ```
npm run generate:swagger:[api-name]
``` **Categorize Changes:**
**Compare Models and Services:** **🔴 Breaking (Critical):**
- Removed properties from response models
```bash - Changed property types (string → number, object → array)
diff -u /tmp/[api-name].backup/models.ts generated/swagger/[api-name]/models.ts - Removed endpoints
diff -u /tmp/[api-name].backup/services.ts generated/swagger/[api-name]/services.ts - Optional → required fields in request models
``` - Removed enum values
- Changed endpoint paths or HTTP methods
**Categorize Changes:**
**⚠️ Warnings (Review Required):**
**🔴 Breaking (Critical):** - Property renamed (old removed + new added)
- Removed properties from response models - Changed default values
- Changed property types (string → number, object → array) - Changed validation rules (min/max length, pattern)
- Removed endpoints - Added required request fields
- Optional → required fields in request models - Changed parameter locations (query → body)
- Removed enum values
- Changed endpoint paths or HTTP methods **✅ Compatible (Safe):**
- Added properties to response models
**⚠️ Warnings (Review Required):** - New endpoints
- Property renamed (old removed + new added) - Added optional parameters
- Changed default values - New enum values
- Changed validation rules (min/max length, pattern) - Required → optional fields
- Added required request fields
- Changed parameter locations (query → body) ### Step 3: Impact Analysis
**✅ Compatible (Safe):** For each breaking or warning change, analyze codebase impact:
- Added properties to response models
- New endpoints ```bash
- Added optional parameters # Find all imports from affected API
- New enum values grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts"
- Required → optional fields
# Find usages of specific removed/changed items
### Step 3: Impact Analysis grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts"
```
For each breaking or warning change, analyze codebase impact:
**Document:**
```bash - Affected files (with line numbers)
# Find all imports from affected API - Services impacted
grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts" - Components/stores using affected services
- Estimated refactoring effort (hours)
# Find usages of specific removed/changed items
grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts" ### Step 4: Generate Migration Strategy
```
Based on breaking change severity:
**Document:**
- Affected files (with line numbers) **High Impact (>5 breaking changes or critical endpoints):**
- Services impacted 1. Create dedicated migration branch from develop
- Components/stores using affected services 2. Document all changes in migration guide
- Estimated refactoring effort (hours) 3. Update data-access services incrementally
4. Update affected stores and components
### Step 4: Generate Migration Strategy 5. Comprehensive test coverage
6. Coordinate deployment with backend team
Based on breaking change severity:
**Medium Impact (2-5 breaking changes):**
**High Impact (>5 breaking changes or critical endpoints):** 1. Fix TypeScript compilation errors
1. Create dedicated migration branch from develop 2. Update affected data-access services
2. Document all changes in migration guide 3. Update tests
3. Update data-access services incrementally 4. Run affected test suite
4. Update affected stores and components 5. Deploy with monitoring
5. Comprehensive test coverage
6. Coordinate deployment with backend team **Low Impact (1 breaking change, minor impact):**
1. Apply minimal updates
**Medium Impact (2-5 breaking changes):** 2. Verify tests pass
1. Fix TypeScript compilation errors 3. Deploy normally
2. Update affected data-access services
3. Update tests ### Step 5: Execute Generation
4. Run affected test suite
5. Deploy with monitoring If user approves proceeding after impact analysis:
**Low Impact (1 breaking change, minor impact):** ```bash
1. Apply minimal updates # All APIs
2. Verify tests pass npm run generate:swagger
3. Deploy normally
# Specific API (if api-name provided)
### Step 5: Execute Generation npm run generate:swagger:[api-name]
```
If user approves proceeding after impact analysis:
### Step 6: Verify Unicode Cleanup
```bash
# All APIs Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify:
npm run generate:swagger
```bash
# Specific API (if api-name provided) # Scan for remaining Unicode issues
npm run generate:swagger:[api-name] grep -r "\\\\u00" generated/swagger/ || echo "✅ No Unicode issues"
``` ```
### Step 6: Verify Unicode Cleanup If issues remain, run manually:
Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify: ```bash
node tools/fix-files.js
```bash ```
# Scan for remaining Unicode issues
grep -r "\\\\u00" generated/swagger/ || echo "✅ No Unicode issues" ### Step 7: TypeScript Validation
```
```bash
If issues remain, run manually: # Full TypeScript compilation check
npx tsc --noEmit
```bash
node tools/fix-files.js # If errors, show affected files
``` npx tsc --noEmit | head -20
```
### Step 7: TypeScript Validation
Document compilation errors:
```bash - Missing properties
# Full TypeScript compilation check - Type mismatches
npx tsc --noEmit - Incompatible signatures
# If errors, show affected files ### Step 8: Run Affected Tests
npx tsc --noEmit | head -20
``` ```bash
# Run tests for affected projects
Document compilation errors: npx nx affected:test --skip-nx-cache --base=HEAD~1
- Missing properties
- Type mismatches # Lint affected projects
- Incompatible signatures npx nx affected:lint --base=HEAD~1
```
### Step 8: Run Affected Tests
Monitor test failures and categorize:
```bash - Mock data mismatches (update fixtures)
# Run tests for affected projects - Type assertion failures (update test types)
npx nx affected:test --skip-nx-cache --base=HEAD~1 - Integration test failures (API contract changes)
# Lint affected projects ### Step 9: Generate Comprehensive Report
npx nx affected:lint --base=HEAD~1
``` ```
API Sync Manager Report
Monitor test failures and categorize: =======================
- Mock data mismatches (update fixtures)
- Type assertion failures (update test types) API: [api-name | all]
- Integration test failures (API contract changes) Sync Date: [timestamp]
Generation: ✅ Success / ❌ Failed
### Step 9: Generate Comprehensive Report
📊 Change Summary
``` -----------------
API Sync Manager Report Breaking Changes: XX
======================= Warnings: XX
Compatible Changes: XX
API: [api-name | all] Files Modified: XX
Sync Date: [timestamp]
Generation: ✅ Success / ❌ Failed 🔴 Breaking Changes
-------------------
📊 Change Summary 1. [API Name] - Removed Property: OrderResponse.deliveryDate
----------------- Impact: Medium (2 files affected)
Breaking Changes: XX Files:
Warnings: XX - libs/oms/data-access/src/lib/services/order.service.ts:45
Compatible Changes: XX - libs/oms/feature/order-detail/src/lib/component.ts:78
Files Modified: XX Fix Strategy: Remove references or use alternativeDate field
Estimated Effort: 30 minutes
🔴 Breaking Changes
------------------- 2. [API Name] - Type Changed: ProductResponse.price (string → number)
1. [API Name] - Removed Property: OrderResponse.deliveryDate Impact: High (1 file + cascading changes)
Impact: Medium (2 files affected) Files:
Files: - libs/catalogue/data-access/src/lib/services/product.service.ts:32
- libs/oms/data-access/src/lib/services/order.service.ts:45 Fix Strategy: Remove string parsing, update price calculations
- libs/oms/feature/order-detail/src/lib/component.ts:78 Estimated Effort: 1 hour
Fix Strategy: Remove references or use alternativeDate field
Estimated Effort: 30 minutes ⚠️ Warnings (Review Required)
------------------------------
2. [API Name] - Type Changed: ProductResponse.price (string → number) 1. [API Name] - Possible Rename: CustomerResponse.customerName → fullName
Impact: High (1 file + cascading changes) Action: Verify with backend team if intentional
Files: Migration: Update references if confirmed rename
- libs/catalogue/data-access/src/lib/services/product.service.ts:32
Fix Strategy: Remove string parsing, update price calculations 2. [API Name] - New Required Field: CreateOrderRequest.taxId
Estimated Effort: 1 hour Action: Update order creation flows to provide taxId
Impact: 3 order creation forms
⚠️ Warnings (Review Required)
------------------------------ ✅ Compatible Changes
1. [API Name] - Possible Rename: CustomerResponse.customerName → fullName ---------------------
Action: Verify with backend team if intentional 1. [API Name] - Added Property: OrderResponse.estimatedDelivery
Migration: Update references if confirmed rename 2. [API Name] - New Endpoint: GET /api/v2/orders/bulk
3. [API Name] - Added Optional Parameter: includeArchived to GET /orders
2. [API Name] - New Required Field: CreateOrderRequest.taxId
Action: Update order creation flows to provide taxId 📊 Validation Results
Impact: 3 order creation forms ---------------------
TypeScript Compilation: ✅ Pass / ❌ XX errors
✅ Compatible Changes Unicode Cleanup: ✅ Complete
--------------------- Tests: XX/XX passing (YY affected)
1. [API Name] - Added Property: OrderResponse.estimatedDelivery Lint: ✅ Pass / ⚠️ XX warnings
2. [API Name] - New Endpoint: GET /api/v2/orders/bulk
3. [API Name] - Added Optional Parameter: includeArchived to GET /orders ❌ Test Failures (if any)
-------------------------
📊 Validation Results 1. order.service.spec.ts - Mock response missing deliveryDate
--------------------- Fix: Update mock fixture
TypeScript Compilation: ✅ Pass / ❌ XX errors 2. product.component.spec.ts - Price type assertion failed
Unicode Cleanup: ✅ Complete Fix: Change expect(price).toBe("10.99") → expect(price).toBe(10.99)
Tests: XX/XX passing (YY affected)
Lint: ✅ Pass / ⚠️ XX warnings 💡 Migration Strategy
---------------------
❌ Test Failures (if any) Approach: [High/Medium/Low Impact]
------------------------- Estimated Total Effort: [hours]
1. order.service.spec.ts - Mock response missing deliveryDate
Fix: Update mock fixture Steps:
2. product.component.spec.ts - Price type assertion failed 1. [Fix compilation errors in data-access services]
Fix: Change expect(price).toBe("10.99") → expect(price).toBe(10.99) 2. [Update test mocks and fixtures]
3. [Update components using affected properties]
💡 Migration Strategy 4. [Run full test suite]
--------------------- 5. [Deploy with backend coordination]
Approach: [High/Medium/Low Impact]
Estimated Total Effort: [hours] 🎯 Recommendation
-----------------
Steps: [One of the following:]
1. [Fix compilation errors in data-access services] ✅ Safe to proceed - Only compatible changes detected
2. [Update test mocks and fixtures] ⚠️ Proceed with caution - Fix breaking changes before deployment
3. [Update components using affected properties] 🔴 Coordinate with backend - High impact changes require migration plan
4. [Run full test suite] 🛑 Block regeneration - Critical breaking changes, backend rollback needed
5. [Deploy with backend coordination]
📋 Next Steps
🎯 Recommendation -------------
----------------- [Specific actions user should take]
[One of the following:] - [ ] Fix compilation errors in [files]
✅ Safe to proceed - Only compatible changes detected - [ ] Update test mocks in [files]
⚠️ Proceed with caution - Fix breaking changes before deployment - [ ] Coordinate deployment timing with backend team
🔴 Coordinate with backend - High impact changes require migration plan - [ ] Monitor [specific endpoints] after deployment
🛑 Block regeneration - Critical breaking changes, backend rollback needed ```
📋 Next Steps ### Step 10: Cleanup
-------------
[Specific actions user should take] ```bash
- [ ] Fix compilation errors in [files] # Remove temporary backup
- [ ] Update test mocks in [files] rm -rf /tmp/[api-name].backup
- [ ] Coordinate deployment timing with backend team
- [ ] Monitor [specific endpoints] after deployment # Or restore if generation needs to be reverted
``` # cp -r /tmp/[api-name].backup generated/swagger/[api-name]
```
### Step 10: Cleanup
## Error Handling
```bash
# Remove temporary backup **Generation Fails:**
rm -rf /tmp/[api-name].backup - Check OpenAPI spec URLs in package.json scripts
- Verify backend API is accessible
# Or restore if generation needs to be reverted - Check for malformed OpenAPI specification
# cp -r /tmp/[api-name].backup generated/swagger/[api-name]
``` **Unicode Cleanup Fails:**
- Run `node tools/fix-files.js` manually
## Error Handling - Check for new Unicode patterns not covered by script
**Generation Fails:** **TypeScript Compilation Errors:**
- Check OpenAPI spec URLs in package.json scripts - Review breaking changes section in report
- Verify backend API is accessible - Update affected data-access services first
- Check for malformed OpenAPI specification - Fix cascading type errors in consumers
**Unicode Cleanup Fails:** **Test Failures:**
- Run `node tools/fix-files.js` manually - Update mock data to match new API contracts
- Check for new Unicode patterns not covered by script - Fix type assertions in tests
- Update integration test expectations
**TypeScript Compilation Errors:**
- Review breaking changes section in report **Diff Detection Issues:**
- Update affected data-access services first - Ensure clean git state before running
- Fix cascading type errors in consumers - Check file permissions on generated/ directory
- Verify backup location is writable
**Test Failures:**
- Update mock data to match new API contracts ## Advanced Usage
- Fix type assertions in tests
- Update integration test expectations **Compare Specific Endpoints Only:**
**Diff Detection Issues:** ```bash
- Ensure clean git state before running # Extract and compare specific interface
- Check file permissions on generated/ directory grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts
- Verify backup location is writable grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts
```
## Advanced Usage
**Bulk API Sync with Change Detection:**
**Compare Specific Endpoints Only:**
Run pre-generation detection for all APIs before regenerating:
```bash
# Extract and compare specific interface ```bash
grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts 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
grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts echo "Checking $api..."
``` # Run detection workflow for each
done
**Bulk API Sync with Change Detection:** ```
Run pre-generation detection for all APIs before regenerating: Generate consolidated report across all APIs before committing.
```bash **Rollback Procedure:**
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..." ```bash
# Run detection workflow for each # If sync causes critical issues
done git checkout generated/swagger/[api-name]
``` git clean -fd generated/swagger/[api-name]
Generate consolidated report across all APIs before committing. # Or restore from backup
cp -r generated/swagger.backup.[timestamp]/* generated/swagger/
**Rollback Procedure:** ```
```bash ## Integration with Git Workflow
# If sync causes critical issues
git checkout generated/swagger/[api-name] **Recommended Commit Message:**
git clean -fd generated/swagger/[api-name]
```
# Or restore from backup feat(api): sync [api-name] API client [TASK-####]
cp -r generated/swagger.backup.[timestamp]/* generated/swagger/
``` Breaking changes:
- Removed OrderResponse.deliveryDate (use estimatedDelivery)
## Integration with Git Workflow - Changed ProductResponse.price type (string → number)
**Recommended Commit Message:** Compatible changes:
- Added OrderResponse.estimatedDelivery
``` - New endpoint GET /api/v2/orders/bulk
feat(api): sync [api-name] API client [TASK-####] ```
Breaking changes: **Branch Strategy:**
- Removed OrderResponse.deliveryDate (use estimatedDelivery)
- Changed ProductResponse.price type (string → number) - Low impact: Commit to feature branch directly
- Medium impact: Create `sync/[api-name]-[date]` branch
Compatible changes: - High impact: Create `migration/[api-name]-[version]` branch with migration guide
- Added OrderResponse.estimatedDelivery
- New endpoint GET /api/v2/orders/bulk ## References
```
- CLAUDE.md API Integration section
**Branch Strategy:** - package.json swagger generation scripts
- tools/fix-files.js for Unicode cleanup logic
- Low impact: Commit to feature branch directly - Semantic Versioning: https://semver.org
- 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

View File

@@ -1,171 +1,177 @@
--- ---
name: arch-docs 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". 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 # Architecture Documentation
Generate comprehensive architecture documentation using modern frameworks and best practices. Generate comprehensive architecture documentation using modern frameworks and best practices.
## When to Use ## Available Frameworks
- Creating or updating architecture documentation ### C4 Model
- Generating C4 model diagrams (Context, Container, Component, Code) Visualize software architecture at different abstraction levels.
- Writing Architecture Decision Records (ADRs)
- Documenting system design and component relationships **Levels:**
- Creating PlantUML or Mermaid diagrams 1. **Context** - System landscape and external actors
2. **Container** - High-level technology choices (apps, databases, etc.)
## Available Frameworks 3. **Component** - Internal structure of containers
4. **Code** - Class/module level detail (optional)
### C4 Model
Best for: Visualizing software architecture at different abstraction levels **When to use:** Visualizing system architecture, creating diagrams for stakeholders at different technical levels.
Levels: See `references/c4-model.md` for detailed patterns and examples.
1. **Context** - System landscape and external actors
2. **Container** - High-level technology choices (apps, databases, etc.) ### Arc42 Template
3. **Component** - Internal structure of containers Comprehensive architecture documentation covering all aspects.
4. **Code** - Class/module level detail (optional)
**Sections:**
See: `@references/c4-model.md` for patterns and examples 1. Introduction and Goals
2. Constraints
### Arc42 Template 3. Context and Scope
Best for: Comprehensive architecture documentation 4. Solution Strategy
5. Building Block View
Sections: 6. Runtime View
1. Introduction and Goals 7. Deployment View
2. Constraints 8. Cross-cutting Concepts
3. Context and Scope 9. Architecture Decisions
4. Solution Strategy 10. Quality Requirements
5. Building Block View 11. Risks and Technical Debt
6. Runtime View 12. Glossary
7. Deployment View
8. Cross-cutting Concepts **When to use:** Creating complete architecture documentation, documenting system-wide concerns, establishing quality goals.
9. Architecture Decisions
10. Quality Requirements See `references/arc42.md` for complete template structure.
11. Risks and Technical Debt
12. Glossary ### Architecture Decision Records (ADRs)
Document individual architectural decisions with context and consequences.
See: `@references/arc42.md` for template structure
**When to use:** Recording significant decisions, documenting trade-offs, tracking architectural evolution.
### Architecture Decision Records (ADRs)
Best for: Documenting individual architectural decisions See `references/adr-template.md` for format and examples.
See: `@references/adr-template.md` for format and examples ## Workflow
## Workflow ### 1. Discovery Phase
Understand the system structure:
### 1. Discovery Phase
```bash ```bash
# Find existing architecture files # Find existing architecture files
find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd" find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd"
# Identify service boundaries # Identify service boundaries
cat nx.json docker-compose.yml cat nx.json docker-compose.yml
# Check for existing ADRs # Check for existing ADRs
ls -la docs/adr/ docs/decisions/ ls -la docs/adr/ docs/decisions/
``` ```
### 2. Analysis Phase ### 2. Analysis Phase
- Analyze codebase structure (`libs/`, `apps/`) - Analyze codebase structure (`libs/`, `apps/`)
- Identify dependencies from `tsconfig.base.json` paths - Identify dependencies from `tsconfig.base.json` paths
- Review service boundaries from `project.json` tags - Review service boundaries from `project.json` tags
- Map data flow from API definitions - Map data flow from API definitions
### 3. Documentation Phase ### 3. Documentation Phase
Based on the request, create appropriate documentation: Choose appropriate output format based on request:
**For C4 diagrams:** **For C4 diagrams:**
``` ```
docs/architecture/ docs/architecture/
├── c4-context.puml ├── c4-context.puml
├── c4-container.puml ├── c4-container.puml
└── c4-component-[name].puml └── c4-component-[name].puml
``` ```
**For ADRs:** **For ADRs:**
``` ```
docs/adr/ docs/adr/
├── 0001-record-architecture-decisions.md ├── 0001-record-architecture-decisions.md
├── 0002-[decision-title].md ├── 0002-[decision-title].md
└── template.md └── template.md
``` ```
**For Arc42:** **For Arc42:**
``` ```
docs/architecture/ docs/architecture/
└── arc42/ └── arc42/
├── 01-introduction.md ├── 01-introduction.md
├── 02-constraints.md ├── 02-constraints.md
└── ... └── ...
``` ```
## ISA-Frontend Specific Context ## ISA-Frontend Context
### Monorepo Structure ### Monorepo Structure
- **apps/**: Angular applications - **apps/**: Angular applications
- **libs/**: Shared libraries organized by domain - **libs/**: Shared libraries organized by domain
- `libs/[domain]/feature/` - Feature modules - `libs/[domain]/feature/` - Feature modules
- `libs/[domain]/data-access/` - State management - `libs/[domain]/data-access/` - State management
- `libs/[domain]/ui/` - Presentational components - `libs/[domain]/ui/` - Presentational components
- `libs/[domain]/util/` - Utilities - `libs/[domain]/util/` - Utilities
### Key Architectural Patterns ### Key Architectural Patterns
- **Nx Monorepo** with strict module boundaries - **Nx Monorepo** with strict module boundaries
- **NgRx Signal Store** for state management - **NgRx Signal Store** for state management
- **Standalone Components** (Angular 20+) - **Standalone Components** (Angular 20+)
- **Domain-Driven Design** library organization - **Domain-Driven Design** library organization
### Documentation Locations ### Documentation Locations
- ADRs: `docs/adr/` - ADRs: `docs/adr/`
- Architecture diagrams: `docs/architecture/` - Architecture diagrams: `docs/architecture/`
- API documentation: Generated from Swagger/OpenAPI - API documentation: Generated from Swagger/OpenAPI
## Output Standards ## Output Standards
### PlantUML Format ### PlantUML Format
```plantuml ```plantuml
@startuml C4_Context @startuml C4_Context
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
Person(user, "User", "System user") Person(user, "User", "System user")
System(system, "ISA System", "Main application") System(system, "ISA System", "Main application")
System_Ext(external, "External API", "Third-party service") System_Ext(external, "External API", "Third-party service")
Rel(user, system, "Uses") Rel(user, system, "Uses")
Rel(system, external, "Calls") Rel(system, external, "Calls")
@enduml @enduml
``` ```
### Mermaid Format ### Mermaid Format
```mermaid ```mermaid
graph TD graph TD
A[User] --> B[ISA App] A[User] --> B[ISA App]
B --> C[API Gateway] B --> C[API Gateway]
C --> D[Backend Services] C --> D[Backend Services]
``` ```
### ADR Format ### ADR Format
```markdown ```markdown
# ADR-XXXX: [Title] # ADR-XXXX: [Title]
## Status ## Status
[Proposed | Accepted | Deprecated | Superseded] [Proposed | Accepted | Deprecated | Superseded]
## Context ## Context
[What is the issue?] [What is the issue?]
## Decision ## Decision
[What was decided?] [What was decided?]
## Consequences ## Consequences
[What are the results?] [What are the results?]
``` ```
## Best Practices ## Best Practices
1. **Start with Context** - Always begin with C4 Level 1 (System Context) 1. **Start with Context** - Always begin with C4 Level 1 (System Context)
2. **Use Consistent Notation** - Stick to one diagramming tool/format 2. **Use Consistent Notation** - Stick to one diagramming tool/format
3. **Keep ADRs Atomic** - One decision per ADR 3. **Keep ADRs Atomic** - One decision per ADR
4. **Version Control** - Commit documentation with code changes 4. **Version Control** - Commit documentation with code changes
5. **Review Regularly** - Architecture docs decay; schedule reviews 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

View File

@@ -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. 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 ## Architectural Rules
**Allowed Dependencies:** **Allowed Dependencies:**

View File

@@ -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. 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 ## Quick Start
### Basic Animation Setup ### 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
<div animate.enter="fade-in" animate.leave="slide-out-down">
```
## Migration from @angular/animations ## Migration from @angular/animations
### Before (Angular Animations) ### Before (Angular Animations)
@@ -390,3 +344,38 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
- GPU hardware acceleration - GPU hardware acceleration
- Standard CSS (transferable skills) - Standard CSS (transferable skills)
- Better performance - 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
<div animate.enter="fade-in" animate.leave="slide-out-down">
```

View File

@@ -1,352 +1,352 @@
--- ---
name: git-workflow 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. 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 # Git Workflow Skill
Enforces Git workflow conventions specific to the ISA-Frontend project. Enforces Git workflow conventions specific to the ISA-Frontend project.
## When to Use ## When to Use
- Creating new branches for features or bugfixes - Creating new branches for features or bugfixes
- Writing commit messages - Writing commit messages
- Creating pull requests - Creating pull requests
- Any Git operations requiring adherence to project conventions - Any Git operations requiring adherence to project conventions
## Core Principles ## Core Principles
### 1. Default Branch is `develop` (NOT `main`) ### 1. Default Branch is `develop` (NOT `main`)
- **All PRs target**: `develop` branch - **All PRs target**: `develop` branch
- **Feature branches from**: `develop` - **Feature branches from**: `develop`
- **Never push directly to**: `develop` or `main` - **Never push directly to**: `develop` or `main`
### 2. Branch Naming Convention ### 2. Branch Naming Convention
**Format**: `<type>/{task-id}-{short-description}` **Format**: `<type>/{task-id}-{short-description}`
**Types**: **Types**:
- `feature/` - New features or enhancements - `feature/` - New features or enhancements
- `bugfix/` - Bug fixes - `bugfix/` - Bug fixes
- `hotfix/` - Emergency production fixes - `hotfix/` - Emergency production fixes
**Rules**: **Rules**:
- Use English kebab-case for descriptions - Use English kebab-case for descriptions
- Start with task/issue ID (e.g., `5391`) - Start with task/issue ID (e.g., `5391`)
- Keep description concise - shorten if too long - Keep description concise - shorten if too long
- Use hyphens to separate words - Use hyphens to separate words
**Examples**: **Examples**:
```bash ```bash
# Good # Good
feature/5391-praemie-checkout-action-card-delivery-order feature/5391-praemie-checkout-action-card-delivery-order
bugfix/6123-fix-login-redirect-loop bugfix/6123-fix-login-redirect-loop
hotfix/7890-critical-payment-error hotfix/7890-critical-payment-error
# Bad # Bad
feature/praemie-checkout # Missing task ID feature/praemie-checkout # Missing task ID
feature/5391_praemie # Using underscores feature/5391_praemie # Using underscores
feature-5391-very-long-description-that-goes-on-forever # Too long feature-5391-very-long-description-that-goes-on-forever # Too long
``` ```
### 3. Conventional Commits (WITHOUT Co-Author Tags) ### 3. Conventional Commits (WITHOUT Co-Author Tags)
**Format**: `<type>(<scope>): <description>` **Format**: `<type>(<scope>): <description>`
**Types**: **Types**:
- `feat`: New feature - `feat`: New feature
- `fix`: Bug fix - `fix`: Bug fix
- `docs`: Documentation only - `docs`: Documentation only
- `style`: Code style (formatting, missing semicolons) - `style`: Code style (formatting, missing semicolons)
- `refactor`: Code restructuring without feature changes - `refactor`: Code restructuring without feature changes
- `perf`: Performance improvements - `perf`: Performance improvements
- `test`: Adding or updating tests - `test`: Adding or updating tests
- `build`: Build system or dependencies - `build`: Build system or dependencies
- `ci`: CI configuration - `ci`: CI configuration
- `chore`: Maintenance tasks - `chore`: Maintenance tasks
**Rules**: **Rules**:
-**NO** "Generated with Claude Code" tags -**NO** "Generated with Claude Code" tags
-**NO** "Co-Authored-By: Claude" tags -**NO** "Co-Authored-By: Claude" tags
- ✅ Keep first line under 72 characters - ✅ Keep first line under 72 characters
- ✅ Use imperative mood ("add" not "added") - ✅ Use imperative mood ("add" not "added")
- ✅ Body optional but recommended for complex changes - ✅ Body optional but recommended for complex changes
**Examples**: **Examples**:
```bash ```bash
# Good # Good
feat(checkout): add bonus card selection for delivery orders feat(checkout): add bonus card selection for delivery orders
fix(crm): resolve customer search filter reset issue fix(crm): resolve customer search filter reset issue
refactor(oms): extract return validation logic into service refactor(oms): extract return validation logic into service
# Bad # Bad
feat(checkout): add bonus card selection feat(checkout): add bonus card selection
Generated with Claude Code Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude <noreply@anthropic.com>
# Also bad # Also bad
Added new feature # Wrong tense Added new feature # Wrong tense
Fix bug # Missing scope Fix bug # Missing scope
``` ```
### 4. Pull Request Creation ### 4. Pull Request Creation
**Target Branch**: Always `develop` **Target Branch**: Always `develop`
**PR Title Format**: Same as conventional commit **PR Title Format**: Same as conventional commit
``` ```
feat(domain): concise description of changes feat(domain): concise description of changes
``` ```
**PR Body Structure**: **PR Body Structure**:
```markdown ```markdown
## Summary ## Summary
- Brief bullet points of changes - Brief bullet points of changes
## Related Tasks ## Related Tasks
- Closes #{task-id} - Closes #{task-id}
- Refs #{related-task} - Refs #{related-task}
## Test Plan ## Test Plan
- [ ] Unit tests added/updated - [ ] Unit tests added/updated
- [ ] E2E attributes added - [ ] E2E attributes added
- [ ] Manual testing completed - [ ] Manual testing completed
## Breaking Changes ## Breaking Changes
None / List breaking changes None / List breaking changes
## Screenshots (if UI changes) ## Screenshots (if UI changes)
[Add screenshots] [Add screenshots]
``` ```
## Common Workflows ## Common Workflows
### Creating a Feature Branch ### Creating a Feature Branch
```bash ```bash
# 1. Update develop # 1. Update develop
git checkout develop git checkout develop
git pull origin develop git pull origin develop
# 2. Create feature branch # 2. Create feature branch
git checkout -b feature/5391-praemie-checkout-action-card git checkout -b feature/5391-praemie-checkout-action-card
# 3. Work and commit # 3. Work and commit
git add . git add .
git commit -m "feat(checkout): add primary bonus card selection logic" git commit -m "feat(checkout): add primary bonus card selection logic"
# 4. Push to remote # 4. Push to remote
git push -u origin feature/5391-praemie-checkout-action-card git push -u origin feature/5391-praemie-checkout-action-card
# 5. Create PR targeting develop (use gh CLI or web UI) # 5. Create PR targeting develop (use gh CLI or web UI)
``` ```
### Creating a Bugfix Branch ### Creating a Bugfix Branch
```bash ```bash
# From develop # From develop
git checkout develop git checkout develop
git pull origin develop git pull origin develop
git checkout -b bugfix/6123-login-redirect-loop git checkout -b bugfix/6123-login-redirect-loop
# Commit # Commit
git commit -m "fix(auth): resolve infinite redirect on logout" git commit -m "fix(auth): resolve infinite redirect on logout"
``` ```
### Creating a Hotfix Branch ### Creating a Hotfix Branch
```bash ```bash
# From main (production) # From main (production)
git checkout main git checkout main
git pull origin main git pull origin main
git checkout -b hotfix/7890-payment-processing-error git checkout -b hotfix/7890-payment-processing-error
# Commit # Commit
git commit -m "fix(checkout): critical payment API timeout handling" git commit -m "fix(checkout): critical payment API timeout handling"
# Merge to both main and develop # Merge to both main and develop
``` ```
## Commit Message Guidelines ## Commit Message Guidelines
### Good Commit Messages ### Good Commit Messages
```bash ```bash
feat(crm): add customer loyalty tier calculation feat(crm): add customer loyalty tier calculation
Implements three-tier loyalty system based on annual spend. Implements three-tier loyalty system based on annual spend.
Includes migration for existing customer data. Includes migration for existing customer data.
Refs #5234 Refs #5234
--- ---
fix(oms): prevent duplicate return submissions fix(oms): prevent duplicate return submissions
Adds debouncing to return form submission and validates Adds debouncing to return form submission and validates
against existing returns in the last 60 seconds. against existing returns in the last 60 seconds.
Closes #5891 Closes #5891
--- ---
refactor(catalogue): extract product search into dedicated service refactor(catalogue): extract product search into dedicated service
Moves search logic from component to ProductSearchService Moves search logic from component to ProductSearchService
for better testability and reusability. for better testability and reusability.
--- ---
perf(remission): optimize remission list query with pagination perf(remission): optimize remission list query with pagination
Reduces initial load time from 3s to 800ms by implementing Reduces initial load time from 3s to 800ms by implementing
cursor-based pagination. cursor-based pagination.
Closes #6234 Closes #6234
``` ```
### Bad Commit Messages ### Bad Commit Messages
```bash ```bash
# Too vague # Too vague
fix: bug fixes fix: bug fixes
# Missing scope # Missing scope
feat: new feature feat: new feature
# Wrong tense # Wrong tense
fixed the login issue fixed the login issue
# Including banned tags # Including banned tags
feat(checkout): add feature feat(checkout): add feature
Generated with Claude Code Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude <noreply@anthropic.com>
``` ```
## Git Configuration Checks ## Git Configuration Checks
### Verify Git Setup ### Verify Git Setup
```bash ```bash
# Check current branch # Check current branch
git branch --show-current git branch --show-current
# Verify remote # Verify remote
git remote -v # Should show origin pointing to ISA-Frontend git remote -v # Should show origin pointing to ISA-Frontend
# Check for uncommitted changes # Check for uncommitted changes
git status git status
``` ```
## Common Mistakes to Avoid ## Common Mistakes to Avoid
```bash ```bash
# ❌ Creating PR against main # ❌ Creating PR against main
gh pr create --base main # WRONG gh pr create --base main # WRONG
# ✅ Always target develop # ✅ Always target develop
gh pr create --base develop # CORRECT gh pr create --base develop # CORRECT
# ❌ Using underscores in branch names # ❌ Using underscores in branch names
git checkout -b feature/5391_my_feature # WRONG git checkout -b feature/5391_my_feature # WRONG
# ✅ Use hyphens # ✅ Use hyphens
git checkout -b feature/5391-my-feature # CORRECT git checkout -b feature/5391-my-feature # CORRECT
# ❌ Adding co-author tags # ❌ Adding co-author tags
git commit -m "feat: something git commit -m "feat: something
Co-Authored-By: Claude <noreply@anthropic.com>" # WRONG Co-Authored-By: Claude <noreply@anthropic.com>" # WRONG
# ✅ Clean commit message # ✅ Clean commit message
git commit -m "feat(scope): something" # CORRECT git commit -m "feat(scope): something" # CORRECT
# ❌ Forgetting task ID in branch name # ❌ Forgetting task ID in branch name
git checkout -b feature/new-checkout-flow # WRONG git checkout -b feature/new-checkout-flow # WRONG
# ✅ Include task ID # ✅ Include task ID
git checkout -b feature/5391-new-checkout-flow # CORRECT git checkout -b feature/5391-new-checkout-flow # CORRECT
``` ```
## Integration with Claude Code ## Integration with Claude Code
When Claude Code creates commits or PRs: When Claude Code creates commits or PRs:
### Commit Creation ### Commit Creation
```bash ```bash
# Claude uses conventional commits WITHOUT attribution # Claude uses conventional commits WITHOUT attribution
git commit -m "feat(checkout): implement bonus card selection git commit -m "feat(checkout): implement bonus card selection
Adds logic for selecting primary bonus card during checkout Adds logic for selecting primary bonus card during checkout
for delivery orders. Includes validation and error handling. for delivery orders. Includes validation and error handling.
Refs #5391" Refs #5391"
``` ```
### PR Creation ### PR Creation
```bash ```bash
# Target develop by default # Target develop by default
gh pr create --base develop \ gh pr create --base develop \
--title "feat(checkout): implement bonus card selection" \ --title "feat(checkout): implement bonus card selection" \
--body "## Summary --body "## Summary
- Add primary bonus card selection logic - Add primary bonus card selection logic
- Implement validation for delivery orders - Implement validation for delivery orders
- Add error handling for API failures - Add error handling for API failures
## Related Tasks ## Related Tasks
- Closes #5391 - Closes #5391
## Test Plan ## Test Plan
- [x] Unit tests added - [x] Unit tests added
- [x] E2E attributes added - [x] E2E attributes added
- [x] Manual testing completed" - [x] Manual testing completed"
``` ```
## Branch Cleanup ## Branch Cleanup
### After PR Merge ### After PR Merge
```bash ```bash
# Update develop # Update develop
git checkout develop git checkout develop
git pull origin develop git pull origin develop
# Delete local feature branch # Delete local feature branch
git branch -d feature/5391-praemie-checkout git branch -d feature/5391-praemie-checkout
# Delete remote branch (usually done by PR merge) # Delete remote branch (usually done by PR merge)
git push origin --delete feature/5391-praemie-checkout git push origin --delete feature/5391-praemie-checkout
``` ```
## Quick Reference ## Quick Reference
```bash ```bash
# Branch naming # Branch naming
feature/{task-id}-{description} feature/{task-id}-{description}
bugfix/{task-id}-{description} bugfix/{task-id}-{description}
hotfix/{task-id}-{description} hotfix/{task-id}-{description}
# Commit format # Commit format
<type>(<scope>): <description> <type>(<scope>): <description>
# Common types # Common types
feat, fix, docs, style, refactor, perf, test, build, ci, chore feat, fix, docs, style, refactor, perf, test, build, ci, chore
# PR target # PR target
Always: develop (NOT main) Always: develop (NOT main)
# Banned in commits # Banned in commits
- "Generated with Claude Code" - "Generated with Claude Code"
- "Co-Authored-By: Claude" - "Co-Authored-By: Claude"
- Any AI attribution - Any AI attribution
``` ```
## Resources ## Resources
- [Conventional Commits](https://www.conventionalcommits.org/) - [Conventional Commits](https://www.conventionalcommits.org/)
- Project PR template: `.github/pull_request_template.md` - Project PR template: `.github/pull_request_template.md`
- Code review standards: `.github/review-instructions.md` - Code review standards: `.github/review-instructions.md`

View File

@@ -1,284 +1,277 @@
--- ---
name: library-creator 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. 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 # Library Creator
## Overview ## 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. 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 ## Required Parameters
Invoke this skill when: Collect these parameters from the user:
- User requests creating a new library - **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils)
- User mentions "new library", "scaffold library", or "create feature" - **layer**: Layer type (feature, data-access, ui, util)
- User wants to add a new domain/layer/feature to the monorepo - **name**: Library name in kebab-case
## Required Parameters ## Library Creation Workflow
Collect these parameters from the user: ### Step 1: Validate Input
- **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils)
- **layer**: Layer type (feature, data-access, ui, util) **Verify Domain:**
- **name**: Library name in kebab-case - Use `docs-researcher` to check `docs/library-reference.md`
- Ensure domain follows existing patterns
## Library Creation Workflow
**Validate Layer:**
### Step 1: Validate Input - Must be one of: feature, data-access, ui, util
**Verify Domain:** **Check Name:**
- Use `docs-researcher` to check `docs/library-reference.md` - Must be kebab-case
- Ensure domain follows existing patterns - Must not conflict with existing libraries
**Validate Layer:** **Determine Path Depth:**
- Must be one of: feature, data-access, ui, util - 3 levels: `libs/domain/layer/name``../../../`
- 4 levels: `libs/domain/type/layer/name``../../../../`
**Check Name:**
- Must be kebab-case ### Step 2: Run Dry-Run
- Must not conflict with existing libraries
Execute Nx generator with `--dry-run` to preview changes:
**Determine Path Depth:**
- 3 levels: `libs/domain/layer/name``../../../` ```bash
- 4 levels: `libs/domain/type/layer/name``../../../../` npx nx generate @nx/angular:library \
--name=[domain]-[layer]-[name] \
### Step 2: Run Dry-Run --directory=libs/[domain]/[layer]/[name] \
--importPath=@isa/[domain]/[layer]/[name] \
Execute Nx generator with `--dry-run` to preview changes: --prefix=[domain] \
--style=css \
```bash --unitTestRunner=vitest \
npx nx generate @nx/angular:library \ --standalone=true \
--name=[domain]-[layer]-[name] \ --skipTests=false \
--directory=libs/[domain]/[layer]/[name] \ --dry-run
--importPath=@isa/[domain]/[layer]/[name] \ ```
--prefix=[domain] \
--style=css \ Review output with user before proceeding.
--unitTestRunner=vitest \
--standalone=true \ ### Step 3: Generate Library
--skipTests=false \
--dry-run Execute without `--dry-run`:
```
```bash
Review output with user before proceeding. npx nx generate @nx/angular:library \
--name=[domain]-[layer]-[name] \
### Step 3: Generate Library --directory=libs/[domain]/[layer]/[name] \
--importPath=@isa/[domain]/[layer]/[name] \
Execute without `--dry-run`: --prefix=[domain] \
--style=css \
```bash --unitTestRunner=vitest \
npx nx generate @nx/angular:library \ --standalone=true \
--name=[domain]-[layer]-[name] \ --skipTests=false
--directory=libs/[domain]/[layer]/[name] \ ```
--importPath=@isa/[domain]/[layer]/[name] \
--prefix=[domain] \ ### Step 4: Add Architectural Tags
--style=css \
--unitTestRunner=vitest \ **CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`.
--standalone=true \
--skipTests=false Run the tagging script:
``` ```bash
node scripts/add-library-tags.js
### Step 4: Add Architectural Tags ```
**CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`. Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`:
Run the tagging script: ```json
```bash {
node scripts/add-library-tags.js "name": "[domain]-[layer]-[name]",
``` "tags": [
"scope:[domain]",
Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`: "type:[layer]"
]
```json }
{ ```
"name": "[domain]-[layer]-[name]",
"tags": [ **Tag Rules:**
"scope:[domain]", - **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`)
"type:[layer]" - **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"]`
**Tag Rules:** - `libs/shared/scanner``["scope:shared", "type:shared"]`
- **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`) - `libs/core/auth` `["scope:core", "type:core"]`
- **Type tag**: `type:[layer]` (e.g., `type:feature`, `type:data-access`, `type:ui`, `type:util`)
**Verification:**
**Examples:** ```bash
- `libs/oms/feature/return-search``["scope:oms", "type:feature"]` # Check tags were added
- `libs/ui/buttons``["scope:ui", "type:ui"]` cat libs/[domain]/[layer]/[name]/project.json | jq '.tags'
- `libs/shared/scanner``["scope:shared", "type:shared"]`
- `libs/core/auth``["scope:core", "type:core"]` # Should output: ["scope:[domain]", "type:[layer]"]
```
**Verification:**
```bash ### Step 5: Configure Vitest with JUnit and Cobertura
# Check tags were added
cat libs/[domain]/[layer]/[name]/project.json | jq '.tags' Update `libs/[path]/vite.config.mts` with this template:
# Should output: ["scope:[domain]", "type:[layer]"] ```typescript
``` /// <reference types='vitest' />
import { defineConfig } from 'vite';
### Step 5: Configure Vitest with JUnit and Cobertura import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
Update `libs/[path]/vite.config.mts` with this template: import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
```typescript export default
/// <reference types='vitest' /> // @ts-expect-error - Vitest reporter tuple types have complex inference issues
import { defineConfig } from 'vite'; defineConfig(() => ({
import angular from '@analogjs/vite-plugin-angular'; root: __dirname,
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; cacheDir: '../../../node_modules/.vite/libs/[path]',
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
test: {
export default watch: false,
// @ts-expect-error - Vitest reporter tuple types have complex inference issues globals: true,
defineConfig(() => ({ environment: 'jsdom',
root: __dirname, include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
cacheDir: '../../../node_modules/.vite/libs/[path]', setupFiles: ['src/test-setup.ts'],
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], reporters: [
test: { 'default',
watch: false, ['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }],
globals: true, ],
environment: 'jsdom', coverage: {
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], reportsDirectory: '../../../coverage/libs/[path]',
setupFiles: ['src/test-setup.ts'], provider: 'v8' as const,
reporters: [ reporter: ['text', 'cobertura'],
'default', },
['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }], },
], }));
coverage: { ```
reportsDirectory: '../../../coverage/libs/[path]',
provider: 'v8' as const, **Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location.
reporter: ['text', 'cobertura'],
}, ### 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"]`
**Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location.
**Run Initial Test:**
### Step 6: Verify Configuration ```bash
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
**Check Path Alias:** ```
- Verify `tsconfig.base.json` was updated
- Should have: `"@isa/[domain]/[layer]/[name]": ["libs/[domain]/[layer]/[name]/src/index.ts"]` **Verify CI/CD Files Created:**
- JUnit XML: `testresults/junit-[library-name].xml`
**Run Initial Test:** - Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml`
```bash
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache ### Step 7: Create Library README
```
Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including:
**Verify CI/CD Files Created:** - Overview and purpose
- JUnit XML: `testresults/junit-[library-name].xml` - Installation/import instructions
- Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml` - API documentation
- Usage examples
### Step 7: Create Library README - Testing information (Vitest + Angular Testing Utilities)
Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including: ### Step 8: Update Library Reference
- Overview and purpose
- Installation/import instructions Add entry to `docs/library-reference.md` under appropriate domain:
- API documentation
- Usage examples ```markdown
- Testing information (Vitest + Angular Testing Utilities) #### `@isa/[domain]/[layer]/[name]`
**Path:** `libs/[domain]/[layer]/[name]`
### Step 8: Update Library Reference **Type:** [Feature/Data Access/UI/Util]
**Testing:** Vitest
Add entry to `docs/library-reference.md` under appropriate domain:
[Brief description]
```markdown ```
#### `@isa/[domain]/[layer]/[name]`
**Path:** `libs/[domain]/[layer]/[name]` ### Step 9: Run Full Validation
**Type:** [Feature/Data Access/UI/Util]
**Testing:** Vitest Execute validation commands to ensure library is properly configured:
[Brief description] ```bash
``` # Lint (includes boundary checks)
npx nx lint [library-name]
### Step 9: Run Full Validation
# Test with coverage
Execute validation commands to ensure library is properly configured: npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
```bash # Build (if buildable)
# Lint (includes boundary checks) npx nx build [library-name]
npx nx lint [library-name]
# Dependency graph
# Test with coverage npx nx graph --focus=[library-name]
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache ```
# Build (if buildable) ### Step 10: Generate Creation Report
npx nx build [library-name]
Provide this structured report to the user:
# Dependency graph
npx nx graph --focus=[library-name] ```
``` Library Created Successfully
============================
### Step 10: Generate Creation Report
Library Name: [domain]-[layer]-[name]
Provide this structured report to the user: Path: libs/[domain]/[layer]/[name]
Import Alias: @isa/[domain]/[layer]/[name]
```
Library Created Successfully ✅ Configuration
============================ ----------------
Test Framework: Vitest with Angular Testing Utilities
Library Name: [domain]-[layer]-[name] Style: CSS
Path: libs/[domain]/[layer]/[name] Standalone: Yes
Import Alias: @isa/[domain]/[layer]/[name] Tags: scope:[domain], type:[layer]
JUnit Reporter: ✅ testresults/junit-[library-name].xml
✅ Configuration Cobertura Coverage: ✅ coverage/libs/[path]/cobertura-coverage.xml
----------------
Test Framework: Vitest with Angular Testing Utilities 📦 Import Statement
Style: CSS -------------------
Standalone: Yes import { Component } from '@isa/[domain]/[layer]/[name]';
Tags: scope:[domain], type:[layer]
JUnit Reporter: ✅ testresults/junit-[library-name].xml 🧪 Test Commands
Cobertura Coverage: ✅ coverage/libs/[path]/cobertura-coverage.xml ----------------
npx nx test [library-name] --skip-nx-cache
📦 Import Statement npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
-------------------
import { Component } from '@isa/[domain]/[layer]/[name]'; 🏗️ Architecture Compliance
--------------------------
🧪 Test Commands Tags enforce module boundaries via @nx/enforce-module-boundaries
---------------- Run lint to check for violations: npx nx lint [library-name]
npx nx test [library-name] --skip-nx-cache
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache 📝 Next Steps
-------------
🏗️ Architecture Compliance 1. Develop library features
-------------------------- 2. Write tests using Vitest + Angular Testing Utilities
Tags enforce module boundaries via @nx/enforce-module-boundaries 3. Add E2E attributes (data-what, data-which) to templates
Run lint to check for violations: npx nx lint [library-name] 4. Update README with usage examples
5. Follow architecture rules (see eslint.config.js for constraints)
📝 Next Steps ```
-------------
1. Develop library features ## Error Handling
2. Write tests using Vitest + Angular Testing Utilities
3. Add E2E attributes (data-what, data-which) to templates **Path depth mismatch:**
4. Update README with usage examples - Count directory levels from workspace root
5. Follow architecture rules (see eslint.config.js for constraints) - Adjust `../` in outputFile and reportsDirectory
```
**TypeScript errors in vite.config.mts:**
## Error Handling - Add `// @ts-expect-error` before `defineConfig()`
**Path depth mismatch:** **Path alias not working:**
- Count directory levels from workspace root - Check tsconfig.base.json
- Adjust `../` in outputFile and reportsDirectory - Run `npx nx reset`
- Restart TypeScript server
**TypeScript errors in vite.config.mts:**
- Add `// @ts-expect-error` before `defineConfig()` **Library already exists:**
- Check `tsconfig.base.json` for existing path alias
**Path alias not working:** - Use Grep to search for existing library name
- Check tsconfig.base.json - Suggest alternative name to user
- Run `npx nx reset`
- Restart TypeScript server ## References
**Library already exists:** - docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections)
- Check `tsconfig.base.json` for existing path alias - docs/library-reference.md (domain patterns)
- Use Grep to search for existing library name - CLAUDE.md (Library Organization, Testing Framework sections)
- Suggest alternative name to user - eslint.config.js (@nx/enforce-module-boundaries configuration)
- scripts/add-library-tags.js (automatic tagging script)
## References - .claude/skills/architecture-validator (boundary validation)
- Nx Angular Library Generator: https://nx.dev/nx-api/angular/generators/library
- docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections) - Nx Enforce Module Boundaries: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module-boundaries
- 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

View File

@@ -1,272 +1,234 @@
--- ---
name: logging 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. 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 # Logging
Ensures consistent and efficient logging using `@isa/core/logging` library. Ensures consistent and efficient logging using `@isa/core/logging` library.
## When to Use ## Core Principles
- Adding logging to new components/services ### 1. Always Use Factory Pattern
- Refactoring existing logging code
- Reviewing code for proper logging patterns ```typescript
- Debugging logging issues import { logger } from '@isa/core/logging';
## Core Principles // ✅ DO
#logger = logger();
### 1. Always Use Factory Pattern
// ❌ DON'T
```typescript constructor(private loggingService: LoggingService) {}
import { logger } from '@isa/core/logging'; ```
// ✅ DO ### 2. Choose Appropriate Log Levels
#logger = logger();
- **Trace**: Fine-grained debugging (method entry/exit)
// ❌ DON'T - **Debug**: Development debugging (variable states)
constructor(private loggingService: LoggingService) {} - **Info**: Runtime information (user actions, events)
``` - **Warn**: Potentially harmful situations
- **Error**: Errors affecting functionality
### 2. Choose Appropriate Log Levels
### 3. Context Patterns
- **Trace**: Fine-grained debugging (method entry/exit)
- **Debug**: Development debugging (variable states) **Static Context** (component level):
- **Info**: Runtime information (user actions, events) ```typescript
- **Warn**: Potentially harmful situations #logger = logger({ component: 'UserProfileComponent' });
- **Error**: Errors affecting functionality ```
### 3. Context Patterns **Dynamic Context** (instance level):
```typescript
**Static Context** (component level): #logger = logger(() => ({
```typescript userId: this.authService.currentUserId,
#logger = logger({ component: 'UserProfileComponent' }); storeId: this.config.storeId
``` }));
```
**Dynamic Context** (instance level):
```typescript **Message Context** (use functions for performance):
#logger = logger(() => ({ ```typescript
userId: this.authService.currentUserId, // ✅ Recommended - lazy evaluation
storeId: this.config.storeId this.#logger.info('Order processed', () => ({
})); orderId: order.id,
``` total: order.total,
timestamp: Date.now()
**Message Context** (use functions for performance): }));
```typescript
// ✅ Recommended - lazy evaluation // ✅ Acceptable - static values
this.#logger.info('Order processed', () => ({ this.#logger.info('Order processed', {
orderId: order.id, orderId: order.id,
total: order.total, status: 'completed'
timestamp: Date.now() });
})); ```
// ✅ Acceptable - static values ## Essential Patterns
this.#logger.info('Order processed', {
orderId: order.id, ### Component Logging
status: 'completed' ```typescript
}); @Component({
``` selector: 'app-product-list',
standalone: true,
## Essential Patterns })
export class ProductListComponent {
### Component Logging #logger = logger({ component: 'ProductListComponent' });
```typescript
@Component({ ngOnInit(): void {
selector: 'app-product-list', this.#logger.info('Component initialized');
standalone: true, }
})
export class ProductListComponent { onAction(id: string): void {
#logger = logger({ component: 'ProductListComponent' }); this.#logger.debug('Action triggered', { id });
}
ngOnInit(): void { }
this.#logger.info('Component initialized'); ```
}
### Service Logging
onAction(id: string): void { ```typescript
this.#logger.debug('Action triggered', { id }); @Injectable({ providedIn: 'root' })
} export class DataService {
} #logger = logger({ service: 'DataService' });
```
fetchData(endpoint: string): Observable<Data> {
### Service Logging this.#logger.debug('Fetching data', { endpoint });
```typescript
@Injectable({ providedIn: 'root' }) return this.http.get<Data>(endpoint).pipe(
export class DataService { tap((data) => this.#logger.info('Data fetched', () => ({
#logger = logger({ service: 'DataService' }); endpoint,
size: data.length
fetchData(endpoint: string): Observable<Data> { }))),
this.#logger.debug('Fetching data', { endpoint }); catchError((error) => {
this.#logger.error('Fetch failed', error, () => ({
return this.http.get<Data>(endpoint).pipe( endpoint,
tap((data) => this.#logger.info('Data fetched', () => ({ status: error.status
endpoint, }));
size: data.length return throwError(() => error);
}))), })
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,
### Error Handling step: this.currentStep,
```typescript attemptNumber: this.retryCount
try { }));
await this.processOrder(orderId); throw error;
} catch (error) { }
this.#logger.error('Order processing failed', error as Error, () => ({ ```
orderId,
step: this.currentStep, ### Hierarchical Context
attemptNumber: this.retryCount ```typescript
})); @Component({
throw error; providers: [
} provideLoggerContext({ feature: 'checkout', module: 'sales' })
``` ]
})
### Hierarchical Context export class CheckoutComponent {
```typescript #logger = logger(() => ({ userId: this.userService.currentUserId }));
@Component({
providers: [ // Logs include: feature, module, userId + message context
provideLoggerContext({ feature: 'checkout', module: 'sales' }) }
] ```
})
export class CheckoutComponent { ## Common Mistakes to Avoid
#logger = logger(() => ({ userId: this.userService.currentUserId }));
```typescript
// Logs include: feature, module, userId + message context // ❌ Don't use console.log
} console.log('User logged in');
``` // ✅ Use logger
this.#logger.info('User logged in');
## Common Mistakes to Avoid
// ❌ Don't create expensive context eagerly
```typescript this.#logger.debug('Processing', {
// ❌ Don't use console.log data: this.computeExpensive() // Always executes
console.log('User logged in'); });
// ✅ Use logger // ✅ Use function for lazy evaluation
this.#logger.info('User logged in'); this.#logger.debug('Processing', () => ({
data: this.computeExpensive() // Only if debug enabled
// ❌ Don't create expensive context eagerly }));
this.#logger.debug('Processing', {
data: this.computeExpensive() // Always executes // ❌ Don't log in tight loops
}); for (const item of items) {
// ✅ Use function for lazy evaluation this.#logger.debug('Item', { item });
this.#logger.debug('Processing', () => ({ }
data: this.computeExpensive() // Only if debug enabled // ✅ Log aggregates
})); this.#logger.debug('Batch processed', () => ({
count: items.length
// ❌ Don't log in tight loops }));
for (const item of items) {
this.#logger.debug('Item', { item }); // ❌ Don't log sensitive data
} this.#logger.info('User auth', { password: user.password });
// ✅ Log aggregates // ✅ Log safe identifiers only
this.#logger.debug('Batch processed', () => ({ this.#logger.info('User auth', { userId: user.id });
count: items.length
})); // ❌ Don't miss error object
this.#logger.error('Failed');
// ❌ Don't log sensitive data // ✅ Include error object
this.#logger.info('User auth', { password: user.password }); this.#logger.error('Failed', error as Error);
// ✅ Log safe identifiers only ```
this.#logger.info('User auth', { userId: user.id });
## Configuration
// ❌ Don't miss error object
this.#logger.error('Failed'); ### App Configuration
// ✅ Include error object ```typescript
this.#logger.error('Failed', error as Error); // app.config.ts
``` import { ApplicationConfig, isDevMode } from '@angular/core';
import {
## Configuration provideLogging, withLogLevel, withSink,
LogLevel, ConsoleLogSink
### App Configuration } from '@isa/core/logging';
```typescript
// app.config.ts export const appConfig: ApplicationConfig = {
import { ApplicationConfig, isDevMode } from '@angular/core'; providers: [
import { provideLogging(
provideLogging, withLogLevel, withSink, withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
LogLevel, ConsoleLogSink withSink(ConsoleLogSink),
} from '@isa/core/logging'; withContext({ app: 'ISA', version: '1.0.0' })
)
export const appConfig: ApplicationConfig = { ]
providers: [ };
provideLogging( ```
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
withSink(ConsoleLogSink), ## Testing
withContext({ app: 'ISA', version: '1.0.0' })
) ```typescript
] import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
}; import { LoggingService } from '@isa/core/logging';
```
describe('MyComponent', () => {
## Testing const createComponent = createComponentFactory({
component: MyComponent,
```typescript mocks: [LoggingService]
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; });
import { LoggingService } from '@isa/core/logging';
it('should log error', () => {
describe('MyComponent', () => { const spectator = createComponent();
const createComponent = createComponentFactory({ const loggingService = spectator.inject(LoggingService);
component: MyComponent,
mocks: [LoggingService] spectator.component.riskyOperation();
});
expect(loggingService.error).toHaveBeenCalledWith(
it('should log error', () => { expect.any(String),
const spectator = createComponent(); expect.any(Error),
const loggingService = spectator.inject(LoggingService); expect.any(Function)
);
spectator.component.riskyOperation(); });
});
expect(loggingService.error).toHaveBeenCalledWith( ```
expect.any(String),
expect.any(Error), ## Additional Resources
expect.any(Function)
); **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
## Code Review Checklist
**Project documentation:**
- [ ] Uses `logger()` factory, not `LoggingService` injection
- [ ] Appropriate log level for each message - Full library documentation: `libs/core/logging/README.md`
- [ ] 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`

View File

@@ -1,192 +1,192 @@
# Logging Quick Reference # Logging API Reference
## API Signatures ## API Signatures
```typescript ```typescript
// Factory // Factory
function logger(ctx?: MaybeLoggerContextFn): LoggerApi function logger(ctx?: MaybeLoggerContextFn): LoggerApi
// Logger API // Logger API
interface LoggerApi { interface LoggerApi {
trace(message: string, context?: MaybeLoggerContextFn): void; trace(message: string, context?: MaybeLoggerContextFn): void;
debug(message: string, context?: MaybeLoggerContextFn): void; debug(message: string, context?: MaybeLoggerContextFn): void;
info(message: string, context?: MaybeLoggerContextFn): void; info(message: string, context?: MaybeLoggerContextFn): void;
warn(message: string, context?: MaybeLoggerContextFn): void; warn(message: string, context?: MaybeLoggerContextFn): void;
error(message: string, error?: Error, context?: MaybeLoggerContextFn): void; error(message: string, error?: Error, context?: MaybeLoggerContextFn): void;
} }
// Types // Types
type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext); type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext);
interface LoggerContext { [key: string]: unknown; } interface LoggerContext { [key: string]: unknown; }
``` ```
## Common Patterns ## Common Patterns
| Pattern | Code | | Pattern | Code |
|---------|------| |---------|------|
| Basic logger | `#logger = logger()` | | Basic logger | `#logger = logger()` |
| Static context | `#logger = logger({ component: 'Name' })` | | Static context | `#logger = logger({ component: 'Name' })` |
| Dynamic context | `#logger = logger(() => ({ id: this.id }))` | | Dynamic context | `#logger = logger(() => ({ id: this.id }))` |
| Log info | `this.#logger.info('Message')` | | Log info | `this.#logger.info('Message')` |
| Log with context | `this.#logger.info('Message', () => ({ key: value }))` | | Log with context | `this.#logger.info('Message', () => ({ key: value }))` |
| Log error | `this.#logger.error('Error', error)` | | Log error | `this.#logger.error('Error', error)` |
| Error with context | `this.#logger.error('Error', error, () => ({ id }))` | | Error with context | `this.#logger.error('Error', error, () => ({ id }))` |
| Component context | `providers: [provideLoggerContext({ feature: 'x' })]` | | Component context | `providers: [provideLoggerContext({ feature: 'x' })]` |
## Configuration ## Configuration
```typescript ```typescript
// app.config.ts // app.config.ts
import { provideLogging, withLogLevel, withSink, withContext, import { provideLogging, withLogLevel, withSink, withContext,
LogLevel, ConsoleLogSink } from '@isa/core/logging'; LogLevel, ConsoleLogSink } from '@isa/core/logging';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideLogging( provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn), withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
withSink(ConsoleLogSink), withSink(ConsoleLogSink),
withContext({ app: 'ISA', version: '1.0.0' }) withContext({ app: 'ISA', version: '1.0.0' })
) )
] ]
}; };
``` ```
## Log Levels ## Log Levels
| Level | Use Case | Example | | Level | Use Case | Example |
|-------|----------|---------| |-------|----------|---------|
| `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` | | `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` |
| `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` | | `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` |
| `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` | | `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` |
| `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` | | `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` |
| `Error` | Errors | `this.#logger.error('Operation failed', error)` | | `Error` | Errors | `this.#logger.error('Operation failed', error)` |
| `Off` | Disable logging | `withLogLevel(LogLevel.Off)` | | `Off` | Disable logging | `withLogLevel(LogLevel.Off)` |
## Decision Trees ## Decision Trees
### Context Type Decision ### Context Type Decision
``` ```
Value changes at runtime? Value changes at runtime?
├─ Yes → () => ({ value: this.getValue() }) ├─ Yes → () => ({ value: this.getValue() })
└─ No → { value: 'static' } └─ No → { value: 'static' }
Computing value is expensive? Computing value is expensive?
├─ Yes → () => ({ data: this.compute() }) ├─ Yes → () => ({ data: this.compute() })
└─ No → Either works └─ No → Either works
``` ```
### Log Level Decision ### Log Level Decision
``` ```
Method flow details? → Trace Method flow details? → Trace
Development debug? → Debug Development debug? → Debug
Runtime information? → Info Runtime information? → Info
Potential problem? → Warn Potential problem? → Warn
Error occurred? → Error Error occurred? → Error
``` ```
## Performance Tips ## Performance Tips
```typescript ```typescript
// ✅ DO: Lazy evaluation // ✅ DO: Lazy evaluation
this.#logger.debug('Data', () => ({ this.#logger.debug('Data', () => ({
result: this.expensive() // Only runs if debug enabled result: this.expensive() // Only runs if debug enabled
})); }));
// ❌ DON'T: Eager evaluation // ❌ DON'T: Eager evaluation
this.#logger.debug('Data', { this.#logger.debug('Data', {
result: this.expensive() // Always runs result: this.expensive() // Always runs
}); });
// ✅ DO: Log aggregates // ✅ DO: Log aggregates
this.#logger.info('Batch done', () => ({ count: items.length })); this.#logger.info('Batch done', () => ({ count: items.length }));
// ❌ DON'T: Log in loops // ❌ DON'T: Log in loops
for (const item of items) { for (const item of items) {
this.#logger.debug('Item', { item }); // Performance hit this.#logger.debug('Item', { item }); // Performance hit
} }
``` ```
## Testing ## Testing
```typescript ```typescript
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { LoggingService } from '@isa/core/logging'; import { LoggingService } from '@isa/core/logging';
describe('MyComponent', () => { describe('MyComponent', () => {
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: MyComponent, component: MyComponent,
mocks: [LoggingService] mocks: [LoggingService]
}); });
it('logs error', () => { it('logs error', () => {
const spectator = createComponent(); const spectator = createComponent();
const logger = spectator.inject(LoggingService); const logger = spectator.inject(LoggingService);
spectator.component.operation(); spectator.component.operation();
expect(logger.error).toHaveBeenCalled(); expect(logger.error).toHaveBeenCalled();
}); });
}); });
``` ```
## Custom Sink ## Custom Sink
```typescript ```typescript
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Sink, LogLevel, LoggerContext } from '@isa/core/logging'; import { Sink, LogLevel, LoggerContext } from '@isa/core/logging';
@Injectable() @Injectable()
export class CustomSink implements Sink { export class CustomSink implements Sink {
log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void { log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void {
// Implementation // Implementation
} }
} }
// Register // Register
provideLogging(withSink(CustomSink)) provideLogging(withSink(CustomSink))
``` ```
## Sink Function (with DI) ## Sink Function (with DI)
```typescript ```typescript
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { SinkFn, LogLevel } from '@isa/core/logging'; import { SinkFn, LogLevel } from '@isa/core/logging';
export const remoteSink: SinkFn = () => { export const remoteSink: SinkFn = () => {
const http = inject(HttpClient); const http = inject(HttpClient);
return (level, message, context, error) => { return (level, message, context, error) => {
if (level === LogLevel.Error) { if (level === LogLevel.Error) {
http.post('/api/logs', { level, message, context, error }).subscribe(); http.post('/api/logs', { level, message, context, error }).subscribe();
} }
}; };
}; };
// Register // Register
provideLogging(withSinkFn(remoteSink)) provideLogging(withSinkFn(remoteSink))
``` ```
## Common Imports ## Common Imports
```typescript ```typescript
// Main imports // Main imports
import { logger, provideLoggerContext } from '@isa/core/logging'; import { logger, provideLoggerContext } from '@isa/core/logging';
// Configuration imports // Configuration imports
import { import {
provideLogging, provideLogging,
withLogLevel, withLogLevel,
withSink, withSink,
withContext, withContext,
LogLevel, LogLevel,
ConsoleLogSink ConsoleLogSink
} from '@isa/core/logging'; } from '@isa/core/logging';
// Type imports // Type imports
import { import {
LoggerApi, LoggerApi,
Sink, Sink,
SinkFn, SinkFn,
LoggerContext LoggerContext
} from '@isa/core/logging'; } from '@isa/core/logging';
``` ```

View File

@@ -1,350 +1,350 @@
# Logging Examples # Logging Examples
Concise real-world examples of logging patterns. Concise real-world examples of logging patterns.
## 1. Component with Observable ## 1. Component with Observable
```typescript ```typescript
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
@Component({ @Component({
selector: 'app-product-list', selector: 'app-product-list',
standalone: true, standalone: true,
}) })
export class ProductListComponent implements OnInit { export class ProductListComponent implements OnInit {
#logger = logger({ component: 'ProductListComponent' }); #logger = logger({ component: 'ProductListComponent' });
constructor(private productService: ProductService) {} constructor(private productService: ProductService) {}
ngOnInit(): void { ngOnInit(): void {
this.#logger.info('Component initialized'); this.#logger.info('Component initialized');
this.loadProducts(); this.loadProducts();
} }
private loadProducts(): void { private loadProducts(): void {
this.productService.getProducts().subscribe({ this.productService.getProducts().subscribe({
next: (products) => { next: (products) => {
this.#logger.info('Products loaded', () => ({ count: products.length })); this.#logger.info('Products loaded', () => ({ count: products.length }));
}, },
error: (error) => { error: (error) => {
this.#logger.error('Failed to load products', error); this.#logger.error('Failed to load products', error);
} }
}); });
} }
} }
``` ```
## 2. Service with HTTP ## 2. Service with HTTP
```typescript ```typescript
import { Injectable, inject } from '@angular/core'; import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class OrderService { export class OrderService {
private http = inject(HttpClient); private http = inject(HttpClient);
#logger = logger({ service: 'OrderService' }); #logger = logger({ service: 'OrderService' });
getOrder(id: string): Observable<Order> { getOrder(id: string): Observable<Order> {
this.#logger.debug('Fetching order', { id }); this.#logger.debug('Fetching order', { id });
return this.http.get<Order>(`/api/orders/${id}`).pipe( return this.http.get<Order>(`/api/orders/${id}`).pipe(
tap((order) => this.#logger.info('Order fetched', () => ({ tap((order) => this.#logger.info('Order fetched', () => ({
id, id,
status: order.status status: order.status
}))), }))),
catchError((error) => { catchError((error) => {
this.#logger.error('Fetch failed', error, () => ({ id, status: error.status })); this.#logger.error('Fetch failed', error, () => ({ id, status: error.status }));
throw error; throw error;
}) })
); );
} }
} }
``` ```
## 3. Hierarchical Context ## 3. Hierarchical Context
```typescript ```typescript
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { logger, provideLoggerContext } from '@isa/core/logging'; import { logger, provideLoggerContext } from '@isa/core/logging';
@Component({ @Component({
selector: 'oms-return-process', selector: 'oms-return-process',
standalone: true, standalone: true,
providers: [ providers: [
provideLoggerContext({ feature: 'returns', module: 'oms' }) provideLoggerContext({ feature: 'returns', module: 'oms' })
], ],
}) })
export class ReturnProcessComponent { export class ReturnProcessComponent {
#logger = logger(() => ({ #logger = logger(() => ({
processId: this.currentProcessId, processId: this.currentProcessId,
step: this.currentStep step: this.currentStep
})); }));
private currentProcessId = crypto.randomUUID(); private currentProcessId = crypto.randomUUID();
private currentStep = 1; private currentStep = 1;
startProcess(orderId: string): void { startProcess(orderId: string): void {
// Logs include: feature, module, processId, step, orderId // Logs include: feature, module, processId, step, orderId
this.#logger.info('Process started', { orderId }); this.#logger.info('Process started', { orderId });
} }
} }
``` ```
## 4. NgRx Effect ## 4. NgRx Effect
```typescript ```typescript
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Actions, createEffect, ofType } from '@ngrx/effects';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
import { map, catchError, tap } from 'rxjs/operators'; import { map, catchError, tap } from 'rxjs/operators';
import { of } from 'rxjs'; import { of } from 'rxjs';
@Injectable() @Injectable()
export class OrdersEffects { export class OrdersEffects {
#logger = logger({ effect: 'OrdersEffects' }); #logger = logger({ effect: 'OrdersEffects' });
loadOrders$ = createEffect(() => loadOrders$ = createEffect(() =>
this.actions$.pipe( this.actions$.pipe(
ofType(OrdersActions.loadOrders), ofType(OrdersActions.loadOrders),
tap((action) => this.#logger.debug('Loading orders', () => ({ tap((action) => this.#logger.debug('Loading orders', () => ({
page: action.page page: action.page
}))), }))),
mergeMap((action) => mergeMap((action) =>
this.orderService.getOrders(action.filters).pipe( this.orderService.getOrders(action.filters).pipe(
map((orders) => { map((orders) => {
this.#logger.info('Orders loaded', () => ({ count: orders.length })); this.#logger.info('Orders loaded', () => ({ count: orders.length }));
return OrdersActions.loadOrdersSuccess({ orders }); return OrdersActions.loadOrdersSuccess({ orders });
}), }),
catchError((error) => { catchError((error) => {
this.#logger.error('Load failed', error); this.#logger.error('Load failed', error);
return of(OrdersActions.loadOrdersFailure({ error })); return of(OrdersActions.loadOrdersFailure({ error }));
}) })
) )
) )
) )
); );
constructor( constructor(
private actions$: Actions, private actions$: Actions,
private orderService: OrderService private orderService: OrderService
) {} ) {}
} }
``` ```
## 5. Guard with Authorization ## 5. Guard with Authorization
```typescript ```typescript
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router'; import { CanActivateFn, Router } from '@angular/router';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
export const authGuard: CanActivateFn = (route, state) => { export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService); const authService = inject(AuthService);
const router = inject(Router); const router = inject(Router);
const log = logger({ guard: 'AuthGuard' }); const log = logger({ guard: 'AuthGuard' });
if (authService.isAuthenticated()) { if (authService.isAuthenticated()) {
log.debug('Access granted', () => ({ route: state.url })); log.debug('Access granted', () => ({ route: state.url }));
return true; return true;
} }
log.warn('Access denied', () => ({ log.warn('Access denied', () => ({
attemptedRoute: state.url, attemptedRoute: state.url,
redirectTo: '/login' redirectTo: '/login'
})); }));
return router.createUrlTree(['/login']); return router.createUrlTree(['/login']);
}; };
``` ```
## 6. HTTP Interceptor ## 6. HTTP Interceptor
```typescript ```typescript
import { HttpInterceptorFn } from '@angular/common/http'; import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { tap, catchError } from 'rxjs/operators'; import { tap, catchError } from 'rxjs/operators';
import { LoggingService } from '@isa/core/logging'; import { LoggingService } from '@isa/core/logging';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => { export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const loggingService = inject(LoggingService); const loggingService = inject(LoggingService);
const startTime = performance.now(); const startTime = performance.now();
loggingService.debug('HTTP Request', () => ({ loggingService.debug('HTTP Request', () => ({
method: req.method, method: req.method,
url: req.url url: req.url
})); }));
return next(req).pipe( return next(req).pipe(
tap((event) => { tap((event) => {
if (event.type === HttpEventType.Response) { if (event.type === HttpEventType.Response) {
loggingService.info('HTTP Response', () => ({ loggingService.info('HTTP Response', () => ({
method: req.method, method: req.method,
url: req.url, url: req.url,
status: event.status, status: event.status,
duration: `${(performance.now() - startTime).toFixed(2)}ms` duration: `${(performance.now() - startTime).toFixed(2)}ms`
})); }));
} }
}), }),
catchError((error) => { catchError((error) => {
loggingService.error('HTTP Error', error, () => ({ loggingService.error('HTTP Error', error, () => ({
method: req.method, method: req.method,
url: req.url, url: req.url,
status: error.status status: error.status
})); }));
return throwError(() => error); return throwError(() => error);
}) })
); );
}; };
``` ```
## 7. Form Validation ## 7. Form Validation
```typescript ```typescript
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
@Component({ @Component({
selector: 'shared-user-form', selector: 'shared-user-form',
standalone: true, standalone: true,
}) })
export class UserFormComponent implements OnInit { export class UserFormComponent implements OnInit {
#logger = logger({ component: 'UserFormComponent' }); #logger = logger({ component: 'UserFormComponent' });
form!: FormGroup; form!: FormGroup;
constructor(private fb: FormBuilder) {} constructor(private fb: FormBuilder) {}
ngOnInit(): void { ngOnInit(): void {
this.form = this.fb.group({ this.form = this.fb.group({
name: ['', Validators.required], name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]] email: ['', [Validators.required, Validators.email]]
}); });
} }
onSubmit(): void { onSubmit(): void {
if (this.form.invalid) { if (this.form.invalid) {
this.#logger.warn('Invalid form submission', () => ({ this.#logger.warn('Invalid form submission', () => ({
errors: this.getFormErrors() errors: this.getFormErrors()
})); }));
return; return;
} }
this.#logger.info('Form submitted'); this.#logger.info('Form submitted');
} }
private getFormErrors(): Record<string, unknown> { private getFormErrors(): Record<string, unknown> {
const errors: Record<string, unknown> = {}; const errors: Record<string, unknown> = {};
Object.keys(this.form.controls).forEach((key) => { Object.keys(this.form.controls).forEach((key) => {
const control = this.form.get(key); const control = this.form.get(key);
if (control?.errors) errors[key] = control.errors; if (control?.errors) errors[key] = control.errors;
}); });
return errors; return errors;
} }
} }
``` ```
## 8. Async Progress Tracking ## 8. Async Progress Tracking
```typescript ```typescript
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ImportService { export class ImportService {
#logger = logger({ service: 'ImportService' }); #logger = logger({ service: 'ImportService' });
importData(file: File): Observable<number> { importData(file: File): Observable<number> {
const importId = crypto.randomUUID(); const importId = crypto.randomUUID();
this.#logger.info('Import started', () => ({ this.#logger.info('Import started', () => ({
importId, importId,
fileName: file.name, fileName: file.name,
fileSize: file.size fileSize: file.size
})); }));
return this.processImport(file).pipe( return this.processImport(file).pipe(
tap((progress) => { tap((progress) => {
if (progress % 25 === 0) { if (progress % 25 === 0) {
this.#logger.debug('Import progress', () => ({ this.#logger.debug('Import progress', () => ({
importId, importId,
progress: `${progress}%` progress: `${progress}%`
})); }));
} }
}), }),
tap({ tap({
complete: () => this.#logger.info('Import completed', { importId }), complete: () => this.#logger.info('Import completed', { importId }),
error: (error) => this.#logger.error('Import failed', error, { importId }) error: (error) => this.#logger.error('Import failed', error, { importId })
}) })
); );
} }
private processImport(file: File): Observable<number> { private processImport(file: File): Observable<number> {
// Implementation // Implementation
} }
} }
``` ```
## 9. Global Error Handler ## 9. Global Error Handler
```typescript ```typescript
import { Injectable, ErrorHandler } from '@angular/core'; import { Injectable, ErrorHandler } from '@angular/core';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
@Injectable() @Injectable()
export class GlobalErrorHandler implements ErrorHandler { export class GlobalErrorHandler implements ErrorHandler {
#logger = logger({ handler: 'GlobalErrorHandler' }); #logger = logger({ handler: 'GlobalErrorHandler' });
handleError(error: Error): void { handleError(error: Error): void {
this.#logger.error('Uncaught error', error, () => ({ this.#logger.error('Uncaught error', error, () => ({
url: window.location.href, url: window.location.href,
userAgent: navigator.userAgent, userAgent: navigator.userAgent,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
})); }));
} }
} }
``` ```
## 10. WebSocket Component ## 10. WebSocket Component
```typescript ```typescript
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'oms-live-orders', selector: 'oms-live-orders',
standalone: true, standalone: true,
}) })
export class LiveOrdersComponent implements OnInit, OnDestroy { export class LiveOrdersComponent implements OnInit, OnDestroy {
#logger = logger({ component: 'LiveOrdersComponent' }); #logger = logger({ component: 'LiveOrdersComponent' });
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor(private wsService: WebSocketService) {} constructor(private wsService: WebSocketService) {}
ngOnInit(): void { ngOnInit(): void {
this.#logger.info('Connecting to WebSocket'); this.#logger.info('Connecting to WebSocket');
this.wsService.connect('orders').pipe( this.wsService.connect('orders').pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe({ ).subscribe({
next: (msg) => this.#logger.debug('Message received', () => ({ next: (msg) => this.#logger.debug('Message received', () => ({
type: msg.type, type: msg.type,
orderId: msg.orderId orderId: msg.orderId
})), })),
error: (error) => this.#logger.error('WebSocket error', error), error: (error) => this.#logger.error('WebSocket error', error),
complete: () => this.#logger.info('WebSocket closed') complete: () => this.#logger.info('WebSocket closed')
}); });
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.#logger.debug('Component destroyed'); this.#logger.debug('Component destroyed');
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
} }
} }
``` ```

View File

@@ -1,235 +1,235 @@
# Logging Troubleshooting # Logging Troubleshooting
## 1. Logs Not Appearing ## 1. Logs Not Appearing
**Problem:** Logger called but nothing in console. **Problem:** Logger called but nothing in console.
**Solutions:** **Solutions:**
```typescript ```typescript
// Check log level // Check log level
provideLogging( provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
) )
// Add sink // Add sink
provideLogging( provideLogging(
withLogLevel(LogLevel.Debug), withLogLevel(LogLevel.Debug),
withSink(ConsoleLogSink) // Required! withSink(ConsoleLogSink) // Required!
) )
// Verify configuration in app.config.ts // Verify configuration in app.config.ts
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideLogging(...) // Must be present provideLogging(...) // Must be present
] ]
}; };
``` ```
## 2. NullInjectorError ## 2. NullInjectorError
**Error:** `NullInjectorError: No provider for LoggingService!` **Error:** `NullInjectorError: No provider for LoggingService!`
**Solution:** **Solution:**
```typescript ```typescript
// app.config.ts // app.config.ts
import { provideLogging, withLogLevel, withSink, import { provideLogging, withLogLevel, withSink,
LogLevel, ConsoleLogSink } from '@isa/core/logging'; LogLevel, ConsoleLogSink } from '@isa/core/logging';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideLogging( provideLogging(
withLogLevel(LogLevel.Debug), withLogLevel(LogLevel.Debug),
withSink(ConsoleLogSink) withSink(ConsoleLogSink)
) )
] ]
}; };
``` ```
## 3. Context Not Showing ## 3. Context Not Showing
**Problem:** Context passed but doesn't appear. **Problem:** Context passed but doesn't appear.
**Check:** **Check:**
```typescript ```typescript
// ✅ Both work: // ✅ Both work:
this.#logger.info('Message', () => ({ id: '123' })); // Function this.#logger.info('Message', () => ({ id: '123' })); // Function
this.#logger.info('Message', { id: '123' }); // Object this.#logger.info('Message', { id: '123' }); // Object
// ❌ Common mistake: // ❌ Common mistake:
const ctx = { id: '123' }; const ctx = { id: '123' };
this.#logger.info('Message', ctx); // Actually works! this.#logger.info('Message', ctx); // Actually works!
// Verify hierarchical merge: // Verify hierarchical merge:
// Global → Component → Instance → Message // Global → Component → Instance → Message
``` ```
## 4. Performance Issues ## 4. Performance Issues
**Problem:** Slow when debug logging enabled. **Problem:** Slow when debug logging enabled.
**Solutions:** **Solutions:**
```typescript ```typescript
// ✅ Use lazy evaluation // ✅ Use lazy evaluation
this.#logger.debug('Data', () => ({ this.#logger.debug('Data', () => ({
expensive: this.compute() // Only if debug enabled expensive: this.compute() // Only if debug enabled
})); }));
// ✅ Reduce log frequency // ✅ Reduce log frequency
this.#logger.debug('Batch', () => ({ this.#logger.debug('Batch', () => ({
count: items.length // Not each item count: items.length // Not each item
})); }));
// ✅ Increase production level // ✅ Increase production level
provideLogging( provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn) withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
) )
``` ```
## 5. Error Object Not Logged ## 5. Error Object Not Logged
**Problem:** Error shows as `[object Object]`. **Problem:** Error shows as `[object Object]`.
**Solution:** **Solution:**
```typescript ```typescript
// ❌ Wrong // ❌ Wrong
this.#logger.error('Failed', { error }); // Don't wrap in object this.#logger.error('Failed', { error }); // Don't wrap in object
// ✅ Correct // ✅ Correct
this.#logger.error('Failed', error as Error, () => ({ this.#logger.error('Failed', error as Error, () => ({
additionalContext: 'value' additionalContext: 'value'
})); }));
``` ```
## 6. TypeScript Errors ## 6. TypeScript Errors
**Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'` **Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'`
**Solution:** **Solution:**
```typescript ```typescript
// ❌ Wrong type // ❌ Wrong type
this.#logger.info('Message', 'string'); // Invalid this.#logger.info('Message', 'string'); // Invalid
// ✅ Correct types // ✅ Correct types
this.#logger.info('Message', { key: 'value' }); this.#logger.info('Message', { key: 'value' });
this.#logger.info('Message', () => ({ key: 'value' })); this.#logger.info('Message', () => ({ key: 'value' }));
``` ```
## 7. Logs in Tests ## 7. Logs in Tests
**Problem:** Test output cluttered with logs. **Problem:** Test output cluttered with logs.
**Solutions:** **Solutions:**
```typescript ```typescript
// Mock logging service // Mock logging service
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { LoggingService } from '@isa/core/logging'; import { LoggingService } from '@isa/core/logging';
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: MyComponent, component: MyComponent,
mocks: [LoggingService] // Mocks all log methods mocks: [LoggingService] // Mocks all log methods
}); });
// Or disable in tests // Or disable in tests
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
provideLogging(withLogLevel(LogLevel.Off)) provideLogging(withLogLevel(LogLevel.Off))
] ]
}); });
``` ```
## 8. Undefined Property Error ## 8. Undefined Property Error
**Error:** `Cannot read property 'X' of undefined` **Error:** `Cannot read property 'X' of undefined`
**Problem:** Accessing uninitialized property in logger context. **Problem:** Accessing uninitialized property in logger context.
**Solutions:** **Solutions:**
```typescript ```typescript
// ❌ Problem // ❌ Problem
#logger = logger(() => ({ #logger = logger(() => ({
userId: this.userService.currentUserId // May be undefined userId: this.userService.currentUserId // May be undefined
})); }));
// ✅ Solution 1: Optional chaining // ✅ Solution 1: Optional chaining
#logger = logger(() => ({ #logger = logger(() => ({
userId: this.userService?.currentUserId ?? 'unknown' userId: this.userService?.currentUserId ?? 'unknown'
})); }));
// ✅ Solution 2: Delay access // ✅ Solution 2: Delay access
ngOnInit() { ngOnInit() {
this.#logger.info('Init', () => ({ this.#logger.info('Init', () => ({
userId: this.userService.currentUserId // Safe here userId: this.userService.currentUserId // Safe here
})); }));
} }
``` ```
## 9. Circular Dependency ## 9. Circular Dependency
**Error:** `NG0200: Circular dependency in DI detected` **Error:** `NG0200: Circular dependency in DI detected`
**Cause:** Service A ← → Service B both inject LoggingService. **Cause:** Service A ← → Service B both inject LoggingService.
**Solution:** **Solution:**
```typescript ```typescript
// ❌ Creates circular dependency // ❌ Creates circular dependency
constructor(private loggingService: LoggingService) {} constructor(private loggingService: LoggingService) {}
// ✅ Use factory (no circular dependency) // ✅ Use factory (no circular dependency)
#logger = logger({ service: 'MyService' }); #logger = logger({ service: 'MyService' });
``` ```
## 10. Custom Sink Not Working ## 10. Custom Sink Not Working
**Problem:** Sink registered but never called. **Problem:** Sink registered but never called.
**Solutions:** **Solutions:**
```typescript ```typescript
// ✅ Correct registration // ✅ Correct registration
provideLogging( provideLogging(
withSink(MySink) // Add to config withSink(MySink) // Add to config
) )
// ✅ Correct signature // ✅ Correct signature
export class MySink implements Sink { export class MySink implements Sink {
log( log(
level: LogLevel, level: LogLevel,
message: string, message: string,
context?: LoggerContext, context?: LoggerContext,
error?: Error error?: Error
): void { ): void {
// Implementation // Implementation
} }
} }
// ✅ Sink function must return function // ✅ Sink function must return function
export const mySinkFn: SinkFn = () => { export const mySinkFn: SinkFn = () => {
const http = inject(HttpClient); const http = inject(HttpClient);
return (level, message, context, error) => { return (level, message, context, error) => {
// Implementation // Implementation
}; };
}; };
``` ```
## Quick Diagnostics ## Quick Diagnostics
```typescript ```typescript
// Enable all logs temporarily // Enable all logs temporarily
provideLogging(withLogLevel(LogLevel.Trace)) provideLogging(withLogLevel(LogLevel.Trace))
// Check imports // Check imports
import { logger } from '@isa/core/logging'; // ✅ Correct import { logger } from '@isa/core/logging'; // ✅ Correct
import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong
// Verify console filters in browser DevTools // Verify console filters in browser DevTools
// Ensure Info, Debug, Warnings are enabled // Ensure Info, Debug, Warnings are enabled
``` ```
## Common Error Messages ## Common Error Messages
| Error | Cause | Fix | | Error | Cause | Fix |
|-------|-------|-----| |-------|-------|-----|
| `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` | | `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` |
| `Type 'X' not assignable` | Wrong context type | Use object or function | | `Type 'X' not assignable` | Wrong context type | Use object or function |
| `Cannot read property 'X'` | Undefined property | Use optional chaining | | `Cannot read property 'X'` | Undefined property | Use optional chaining |
| `Circular dependency` | Service injection | Use `logger()` factory | | `Circular dependency` | Service injection | Use `logger()` factory |
| Stack overflow | Infinite loop in context | Don't call logger in context | | Stack overflow | Infinite loop in context | Don't call logger in context |

View File

@@ -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. 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 ## Core Design System Principles
### 0. Component Libraries First (Most Important) ### 0. Component Libraries First (Most Important)

View File

@@ -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. 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 ## Overview
This skill combines three essential aspects of Angular template development: 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.** **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 ## 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 ### @defer Lazy Loading
#### Basic Usage #### Basic Usage
@@ -97,7 +87,7 @@ This skill combines three essential aspects of Angular template development:
} }
``` ```
#### Triggers #### Common Triggers
| Trigger | Use Case | | 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))` **Multiple triggers:** `@defer (on interaction; on timer(5s))`
**Prefetching:** `@defer (on interaction; prefetch on idle)` **Prefetching:** `@defer (on interaction; prefetch on idle)`
#### Requirements #### Critical Requirements
- Components **MUST be standalone** - Components **MUST be standalone**
- No `@ViewChild`/`@ContentChild` references - No `@ViewChild`/`@ContentChild` references
- Reserve space in `@placeholder` to prevent layout shift - 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 **See [defer-patterns.md](references/defer-patterns.md) for performance optimization, Core Web Vitals impact, bundle size reduction strategies, and real-world examples.**
- ✅ Defer below-the-fold content
- ❌ Never defer above-the-fold (harms LCP)
- ❌ Avoid `immediate`/`timer` during initial render (harms TTI)
- Test with network throttling
### Content Projection ### Content Projection
@@ -156,12 +143,9 @@ This skill combines three essential aspects of Angular template development:
</ui-card> </ui-card>
``` ```
**Fallback content:** `<ng-content select="title">Default Title</ng-content>` **CRITICAL Constraint:** `ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
**Aliasing:** `<h3 ngProjectAs="card-header">Title</h3>`
#### CRITICAL Constraint **See [projection-patterns.md](references/projection-patterns.md) for conditional projection, template-based projection, querying projected content, and modal/form field examples.**
`ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
### Template References ### Template References
@@ -193,6 +177,8 @@ Groups elements without DOM footprint:
</p> </p>
``` ```
**See [template-reference.md](references/template-reference.md) for programmatic rendering, ViewContainerRef patterns, context scoping, and common pitfalls.**
### Variables ### Variables
#### @let (Angular 18.1+) #### @let (Angular 18.1+)
@@ -226,16 +212,14 @@ Groups elements without DOM footprint:
**Class:** `[class.active]="isActive()"` or `[class]="{active: isActive()}"` **Class:** `[class.active]="isActive()"` or `[class]="{active: isActive()}"`
**Style:** `[style.width.px]="width()"` or `[style]="{color: textColor()}"` **Style:** `[style.width.px]="width()"` or `[style]="{color: textColor()}"`
---
## Part 2: E2E Testing Attributes ## Part 2: E2E Testing Attributes
### Purpose ### Purpose
Enable automated end-to-end testing by providing stable selectors for QA automation: Enable automated end-to-end testing by providing stable selectors for QA automation:
- **`data-what`**: Semantic description of element's purpose - **`data-what`**: Semantic description of element's purpose (e.g., `submit-button`, `email-input`)
- **`data-which`**: Unique identifier for specific instances - **`data-which`**: Unique identifier for specific instances (e.g., `registration-form`, `customer-123`)
- **`data-*`**: Additional contextual information - **`data-*`**: Additional contextual information (e.g., `data-status="active"`)
### Naming Conventions ### 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 3. ✅ Ensure `data-which` is unique within the view
4. ✅ Use Angular binding for dynamic values: `[attr.data-*]` 4. ✅ Use Angular binding for dynamic values: `[attr.data-*]`
5. ✅ Avoid including sensitive data in attributes 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 ## 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 6. ✅ Keep ARIA attributes in sync with visual states
7. ✅ Test with screen readers (NVDA, JAWS, VoiceOver) 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 ## 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
<a
[routerLink]="['/orders', orderId]"
data-what="order-link"
[attr.data-which]="orderId"
[attr.aria-label]="'View order ' + orderNumber">
View Order #{{ orderNumber }}
</a>
```
### Dialog with All Attributes ### Dialog with All Attributes
```html ```html
@@ -387,109 +358,7 @@ Ensure web applications are accessible to all users, including those using assis
</div> </div>
``` ```
### Complete Form Example **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.**
```html
<form
(ngSubmit)="onSubmit()"
data-what="registration-form"
data-which="user-signup"
role="form"
aria-labelledby="form-title">
<h2 id="form-title">User Registration</h2>
<div class="form-field">
<label for="username-input">Username</label>
<input
id="username-input"
type="text"
[(ngModel)]="username"
name="username"
data-what="username-input"
data-which="registration-form"
aria-required="true"
aria-describedby="username-hint" />
<span id="username-hint">Must be at least 3 characters</span>
</div>
<div class="form-field">
<label for="email-input">Email</label>
<input
id="email-input"
type="email"
[(ngModel)]="email"
name="email"
data-what="email-input"
data-which="registration-form"
aria-required="true"
[attr.aria-invalid]="emailError ? 'true' : null"
aria-describedby="email-error" />
@if (emailError) {
<span
id="email-error"
role="alert"
aria-live="polite">
{{ emailError }}
</span>
}
</div>
<div class="form-actions">
<button
type="submit"
data-what="submit-button"
data-which="registration-form"
[attr.aria-disabled]="!isValid()"
aria-label="Submit registration form">
Register
</button>
<button
type="button"
(click)="onCancel()"
data-what="cancel-button"
data-which="registration-form"
aria-label="Cancel registration">
Cancel
</button>
</div>
</form>
```
### Conditional Rendering with Attributes
```typescript
@if (isLoading()) {
<div
data-what="loading-spinner"
data-which="user-profile"
role="status"
aria-live="polite"
aria-label="Loading user profile">
<mat-spinner />
</div>
} @else if (error()) {
<div
data-what="error-message"
data-which="user-profile"
role="alert"
aria-live="assertive">
{{ error() }}
</div>
} @else {
<div
data-what="profile-content"
data-which="user-profile"
role="region"
aria-labelledby="profile-title">
<h2 id="profile-title">{{ user().name }}</h2>
<!-- profile content -->
</div>
}
```
---
## Validation Checklist ## Validation Checklist
@@ -501,7 +370,6 @@ Before considering template complete:
- [ ] Complex expressions moved to `computed()` in component - [ ] Complex expressions moved to `computed()` in component
- [ ] @defer used for below-the-fold or heavy components - [ ] @defer used for below-the-fold or heavy components
- [ ] Content projection using ng-content with proper selectors (if applicable) - [ ] 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+) - [ ] Using @let for template-scoped variables (Angular 18.1+)
### E2E Attributes ### E2E Attributes
@@ -510,7 +378,6 @@ Before considering template complete:
- [ ] All links have `data-what` and `data-which` - [ ] All links have `data-what` and `data-which`
- [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers - [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers
- [ ] No duplicate `data-which` values within the same view - [ ] No duplicate `data-which` values within the same view
- [ ] Additional `data-*` attributes for contextual information (if needed)
### ARIA Accessibility ### ARIA Accessibility
- [ ] All interactive elements have appropriate ARIA labels - [ ] All interactive elements have appropriate ARIA labels
@@ -520,16 +387,13 @@ Before considering template complete:
- [ ] Dialogs have `role="dialog"`, `aria-modal`, and label relationships - [ ] Dialogs have `role="dialog"`, `aria-modal`, and label relationships
- [ ] Dynamic state changes reflected in ARIA attributes - [ ] Dynamic state changes reflected in ARIA attributes
- [ ] Keyboard accessibility (tabindex, enter/space handlers where needed) - [ ] Keyboard accessibility (tabindex, enter/space handlers where needed)
- [ ] Screen reader testing completed (if applicable)
### Combined Standards ### Combined Standards
- [ ] Every interactive element has BOTH E2E and ARIA attributes - [ ] 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 - [ ] Dynamic bindings use `[attr.*]` syntax correctly
- [ ] No accessibility violations (semantic HTML preferred over ARIA) - [ ] No accessibility violations (semantic HTML preferred over ARIA)
---
## Migration Guide ## Migration Guide
### From Legacy Angular Syntax ### From Legacy Angular Syntax
@@ -549,31 +413,27 @@ Before considering template complete:
3. **Add ARIA attributes**: appropriate role, label, and state attributes 3. **Add ARIA attributes**: appropriate role, label, and state attributes
4. **Test**: verify selectors work in E2E tests, validate with screen readers 4. **Test**: verify selectors work in E2E tests, validate with screen readers
---
## Reference Files ## Reference Files
For detailed examples and advanced patterns, see: For detailed examples and advanced patterns, see:
### Angular Syntax References ### Angular Syntax References
- `references/control-flow-reference.md` - @if/@for/@switch patterns - `references/control-flow-reference.md` - Advanced @if/@for/@switch patterns, nested loops, filtering
- `references/defer-patterns.md` - Lazy loading strategies - `references/defer-patterns.md` - Lazy loading strategies, Core Web Vitals, performance optimization
- `references/projection-patterns.md` - Advanced ng-content - `references/projection-patterns.md` - Advanced ng-content, conditional projection, template-based patterns
- `references/template-reference.md` - ng-template/ng-container - `references/template-reference.md` - ng-template/ng-container, programmatic rendering, ViewContainerRef
### E2E Testing References ### 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 ### 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 ### 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/` Search with: `grep -r "pattern" references/`
---
## Quick Reference Summary ## Quick Reference Summary
**Every interactive element needs:** **Every interactive element needs:**

View File

@@ -1,192 +1,192 @@
--- ---
name: test-migration 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. 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 # Jest to Vitest Migration Patterns
Quick reference for test framework migration syntax. Quick reference for test framework migration syntax.
## Import Changes ## Import Changes
```typescript ```typescript
// REMOVE // REMOVE
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
// ADD // ADD
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { describe, it, expect, beforeEach, vi } from 'vitest';
``` ```
## Mock Migration ## Mock Migration
| Jest | Vitest | | Jest | Vitest |
|------|--------| |------|--------|
| `jest.fn()` | `vi.fn()` | | `jest.fn()` | `vi.fn()` |
| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` | | `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` |
| `jest.mock('module')` | `vi.mock('module')` | | `jest.mock('module')` | `vi.mock('module')` |
| `jest.useFakeTimers()` | `vi.useFakeTimers()` | | `jest.useFakeTimers()` | `vi.useFakeTimers()` |
| `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` | | `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` |
| `jest.useRealTimers()` | `vi.useRealTimers()` | | `jest.useRealTimers()` | `vi.useRealTimers()` |
| `jest.clearAllMocks()` | `vi.clearAllMocks()` | | `jest.clearAllMocks()` | `vi.clearAllMocks()` |
| `jest.resetAllMocks()` | `vi.resetAllMocks()` | | `jest.resetAllMocks()` | `vi.resetAllMocks()` |
## Spectator → TestBed ## Spectator → TestBed
### Component Testing ### Component Testing
```typescript ```typescript
// OLD (Spectator) // OLD (Spectator)
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: MyComponent, component: MyComponent,
imports: [CommonModule], imports: [CommonModule],
mocks: [MyService] mocks: [MyService]
}); });
let spectator: Spectator<MyComponent>; let spectator: Spectator<MyComponent>;
beforeEach(() => spectator = createComponent()); beforeEach(() => spectator = createComponent());
// NEW (TestBed) // NEW (TestBed)
let fixture: ComponentFixture<MyComponent>; let fixture: ComponentFixture<MyComponent>;
let component: MyComponent; let component: MyComponent;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [MyComponent], imports: [MyComponent],
providers: [{ provide: MyService, useValue: mockService }] providers: [{ provide: MyService, useValue: mockService }]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(MyComponent); fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
}); });
``` ```
### Service Testing ### Service Testing
```typescript ```typescript
// OLD (Spectator) // OLD (Spectator)
const createService = createServiceFactory({ const createService = createServiceFactory({
service: MyService, service: MyService,
mocks: [HttpClient] mocks: [HttpClient]
}); });
let spectator: SpectatorService<MyService>; let spectator: SpectatorService<MyService>;
beforeEach(() => spectator = createService()); beforeEach(() => spectator = createService());
// NEW (TestBed) // NEW (TestBed)
let service: MyService; let service: MyService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
MyService, MyService,
{ provide: HttpClient, useValue: mockHttp } { provide: HttpClient, useValue: mockHttp }
] ]
}); });
service = TestBed.inject(MyService); service = TestBed.inject(MyService);
}); });
``` ```
## Query Selectors ## Query Selectors
| Spectator | Angular/Native | | Spectator | Angular/Native |
|-----------|---------------| |-----------|---------------|
| `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` | | `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` |
| `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` | | `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` |
| `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` | | `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` |
## Events ## Events
| Spectator | Native | | Spectator | Native |
|-----------|--------| |-----------|--------|
| `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` | | `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` |
| `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` | | `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` |
| `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` | | `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` |
## Matchers ## Matchers
| Spectator | Standard | | Spectator | Standard |
|-----------|----------| |-----------|----------|
| `toHaveText('text')` | `expect(el.textContent).toContain('text')` | | `toHaveText('text')` | `expect(el.textContent).toContain('text')` |
| `toExist()` | `toBeTruthy()` | | `toExist()` | `toBeTruthy()` |
| `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` | | `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` |
| `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` | | `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` |
## Async Patterns ## Async Patterns
```typescript ```typescript
// OLD (callback) // OLD (callback)
it('should emit', (done) => { it('should emit', (done) => {
service.data$.subscribe(val => { service.data$.subscribe(val => {
expect(val).toBe(expected); expect(val).toBe(expected);
done(); done();
}); });
}); });
// NEW (async/await) // NEW (async/await)
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
it('should emit', async () => { it('should emit', async () => {
const val = await firstValueFrom(service.data$); const val = await firstValueFrom(service.data$);
expect(val).toBe(expected); expect(val).toBe(expected);
}); });
``` ```
## Input/Output Testing ## Input/Output Testing
```typescript ```typescript
// Setting inputs (Angular 17.3+) // Setting inputs (Angular 17.3+)
fixture.componentRef.setInput('title', 'Test'); fixture.componentRef.setInput('title', 'Test');
fixture.detectChanges(); fixture.detectChanges();
// Testing outputs // Testing outputs
const emitSpy = vi.fn(); const emitSpy = vi.fn();
component.myOutput.subscribe(emitSpy); component.myOutput.subscribe(emitSpy);
// trigger action... // trigger action...
expect(emitSpy).toHaveBeenCalledWith(expectedValue); expect(emitSpy).toHaveBeenCalledWith(expectedValue);
``` ```
## Configuration Files ## Configuration Files
### vite.config.mts Template ### vite.config.mts Template
```typescript ```typescript
/// <reference types='vitest' /> /// <reference types='vitest' />
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular'; import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default export default
// @ts-expect-error - Vitest reporter tuple types have complex inference issues // @ts-expect-error - Vitest reporter tuple types have complex inference issues
defineConfig(() => ({ defineConfig(() => ({
root: __dirname, root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth! cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth!
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'], setupFiles: ['src/test-setup.ts'],
reporters: [ reporters: [
'default', 'default',
['junit', { outputFile: '../../../testresults/junit-[name].xml' }], ['junit', { outputFile: '../../../testresults/junit-[name].xml' }],
], ],
coverage: { coverage: {
reportsDirectory: '../../../coverage/libs/[path]', reportsDirectory: '../../../coverage/libs/[path]',
provider: 'v8' as const, provider: 'v8' as const,
reporter: ['text', 'cobertura'], reporter: ['text', 'cobertura'],
}, },
}, },
})); }));
``` ```
### test-setup.ts ### test-setup.ts
```typescript ```typescript
import '@analogjs/vitest-angular/setup-zone'; import '@analogjs/vitest-angular/setup-zone';
``` ```
## Path Depth Reference ## Path Depth Reference
| Library Location | Relative Path Prefix | | Library Location | Relative Path Prefix |
|-----------------|---------------------| |-----------------|---------------------|
| `libs/feature/ui` | `../../` | | `libs/feature/ui` | `../../` |
| `libs/feature/data-access` | `../../` | | `libs/feature/data-access` | `../../` |
| `libs/domain/feature/ui` | `../../../` | | `libs/domain/feature/ui` | `../../../` |
| `libs/domain/feature/data-access/store` | `../../../../` | | `libs/domain/feature/data-access/store` | `../../../../` |