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

View File

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

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.
## When to Use This Skill
Invoke when user wants to:
- Validate import boundaries or architectural rules
- Detect circular dependencies or dependency cycles
- Find layer violations (Feature→Feature, Data Access→Feature)
- Check cross-domain dependencies
- Resolve build failures with circular import warnings
- Mentioned "check architecture", "validate boundaries", "circular dependencies", or "dependency cycles"
## Architectural Rules
**Allowed Dependencies:**

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.
## When to Use This Skill
Apply this skill when:
- **Writing Angular components** with entrance/exit animations
- **Converting @angular/animations** to native CSS @keyframes
- **Implementing animate.enter/animate.leave** in Angular 20+ templates
- **Creating staggered animations** for lists or collections
- **Debugging animation issues** (snap-back, wrong starting positions, choppy playback)
- **Optimizing animation performance** for GPU acceleration
- **Reviewing animation code** for accessibility and best practices
## Quick Start
### Basic Animation Setup
@@ -311,41 +300,6 @@ element.addEventListener('animationend', (e) => {
});
```
## Resources
### references/keyframes-guide.md
Comprehensive deep-dive covering:
- Complete @keyframes syntax reference
- Detailed timing functions and cubic-bezier curves
- Advanced techniques (direction, play-state, @starting-style)
- Performance optimization strategies
- Extensive common patterns library
- Debugging techniques and troubleshooting
**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details.
### assets/animations.css
Ready-to-use CSS file with common animation patterns:
- Fade animations (in/out)
- Slide animations (up/down/left/right)
- Scale animations (in/out)
- Utility animations (spin, shimmer, shake, breathe, attention-pulse)
- Toast/notification animations
- Accessibility (@media prefers-reduced-motion)
**Usage:** Copy this file to project and import in component styles or global styles:
```css
@import 'path/to/animations.css';
```
Then use classes directly:
```html
<div animate.enter="fade-in" animate.leave="slide-out-down">
```
## Migration from @angular/animations
### Before (Angular Animations)
@@ -390,3 +344,38 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
- GPU hardware acceleration
- Standard CSS (transferable skills)
- Better performance
## Resources
### references/keyframes-guide.md
Comprehensive deep-dive covering:
- Complete @keyframes syntax reference
- Detailed timing functions and cubic-bezier curves
- Advanced techniques (direction, play-state, @starting-style)
- Performance optimization strategies
- Extensive common patterns library
- Debugging techniques and troubleshooting
**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details.
### assets/animations.css
Ready-to-use CSS file with common animation patterns:
- Fade animations (in/out)
- Slide animations (up/down/left/right)
- Scale animations (in/out)
- Utility animations (spin, shimmer, shake, breathe, attention-pulse)
- Toast/notification animations
- Accessibility (@media prefers-reduced-motion)
**Usage:** Copy this file to project and import in component styles or global styles:
```css
@import 'path/to/animations.css';
```
Then use classes directly:
```html
<div animate.enter="fade-in" animate.leave="slide-out-down">
```

View File

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

View File

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

View File

@@ -1,272 +1,234 @@
---
name: logging
description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes.
---
# Logging Helper Skill
Ensures consistent and efficient logging using `@isa/core/logging` library.
## When to Use
- Adding logging to new components/services
- Refactoring existing logging code
- Reviewing code for proper logging patterns
- Debugging logging issues
## Core Principles
### 1. Always Use Factory Pattern
```typescript
import { logger } from '@isa/core/logging';
// ✅ DO
#logger = logger();
// ❌ DON'T
constructor(private loggingService: LoggingService) {}
```
### 2. Choose Appropriate Log Levels
- **Trace**: Fine-grained debugging (method entry/exit)
- **Debug**: Development debugging (variable states)
- **Info**: Runtime information (user actions, events)
- **Warn**: Potentially harmful situations
- **Error**: Errors affecting functionality
### 3. Context Patterns
**Static Context** (component level):
```typescript
#logger = logger({ component: 'UserProfileComponent' });
```
**Dynamic Context** (instance level):
```typescript
#logger = logger(() => ({
userId: this.authService.currentUserId,
storeId: this.config.storeId
}));
```
**Message Context** (use functions for performance):
```typescript
// ✅ Recommended - lazy evaluation
this.#logger.info('Order processed', () => ({
orderId: order.id,
total: order.total,
timestamp: Date.now()
}));
// ✅ Acceptable - static values
this.#logger.info('Order processed', {
orderId: order.id,
status: 'completed'
});
```
## Essential Patterns
### Component Logging
```typescript
@Component({
selector: 'app-product-list',
standalone: true,
})
export class ProductListComponent {
#logger = logger({ component: 'ProductListComponent' });
ngOnInit(): void {
this.#logger.info('Component initialized');
}
onAction(id: string): void {
this.#logger.debug('Action triggered', { id });
}
}
```
### Service Logging
```typescript
@Injectable({ providedIn: 'root' })
export class DataService {
#logger = logger({ service: 'DataService' });
fetchData(endpoint: string): Observable<Data> {
this.#logger.debug('Fetching data', { endpoint });
return this.http.get<Data>(endpoint).pipe(
tap((data) => this.#logger.info('Data fetched', () => ({
endpoint,
size: data.length
}))),
catchError((error) => {
this.#logger.error('Fetch failed', error, () => ({
endpoint,
status: error.status
}));
return throwError(() => error);
})
);
}
}
```
### Error Handling
```typescript
try {
await this.processOrder(orderId);
} catch (error) {
this.#logger.error('Order processing failed', error as Error, () => ({
orderId,
step: this.currentStep,
attemptNumber: this.retryCount
}));
throw error;
}
```
### Hierarchical Context
```typescript
@Component({
providers: [
provideLoggerContext({ feature: 'checkout', module: 'sales' })
]
})
export class CheckoutComponent {
#logger = logger(() => ({ userId: this.userService.currentUserId }));
// Logs include: feature, module, userId + message context
}
```
## Common Mistakes to Avoid
```typescript
// ❌ Don't use console.log
console.log('User logged in');
// ✅ Use logger
this.#logger.info('User logged in');
// ❌ Don't create expensive context eagerly
this.#logger.debug('Processing', {
data: this.computeExpensive() // Always executes
});
// ✅ Use function for lazy evaluation
this.#logger.debug('Processing', () => ({
data: this.computeExpensive() // Only if debug enabled
}));
// ❌ Don't log in tight loops
for (const item of items) {
this.#logger.debug('Item', { item });
}
// ✅ Log aggregates
this.#logger.debug('Batch processed', () => ({
count: items.length
}));
// ❌ Don't log sensitive data
this.#logger.info('User auth', { password: user.password });
// ✅ Log safe identifiers only
this.#logger.info('User auth', { userId: user.id });
// ❌ Don't miss error object
this.#logger.error('Failed');
// ✅ Include error object
this.#logger.error('Failed', error as Error);
```
## Configuration
### App Configuration
```typescript
// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import {
provideLogging, withLogLevel, withSink,
LogLevel, ConsoleLogSink
} from '@isa/core/logging';
export const appConfig: ApplicationConfig = {
providers: [
provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
withSink(ConsoleLogSink),
withContext({ app: 'ISA', version: '1.0.0' })
)
]
};
```
## Testing
```typescript
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { LoggingService } from '@isa/core/logging';
describe('MyComponent', () => {
const createComponent = createComponentFactory({
component: MyComponent,
mocks: [LoggingService]
});
it('should log error', () => {
const spectator = createComponent();
const loggingService = spectator.inject(LoggingService);
spectator.component.riskyOperation();
expect(loggingService.error).toHaveBeenCalledWith(
expect.any(String),
expect.any(Error),
expect.any(Function)
);
});
});
```
## Code Review Checklist
- [ ] Uses `logger()` factory, not `LoggingService` injection
- [ ] Appropriate log level for each message
- [ ] Context functions for expensive operations
- [ ] No sensitive information (passwords, tokens, PII)
- [ ] No `console.log` statements
- [ ] Error logs include error object
- [ ] No logging in tight loops
- [ ] Component/service identified in context
- [ ] E2E attributes on interactive elements
## Quick Reference
```typescript
// Import
import { logger, provideLoggerContext } from '@isa/core/logging';
// Create logger
#logger = logger(); // Basic
#logger = logger({ component: 'Name' }); // Static context
#logger = logger(() => ({ id: this.id })); // Dynamic context
// Log messages
this.#logger.trace('Detailed trace');
this.#logger.debug('Debug info');
this.#logger.info('General info', () => ({ key: value }));
this.#logger.warn('Warning');
this.#logger.error('Error', error, () => ({ context }));
// Component context
@Component({
providers: [provideLoggerContext({ feature: 'users' })]
})
```
## Additional Resources
- Full documentation: `libs/core/logging/README.md`
- Examples: `.claude/skills/logging-helper/examples.md`
- Quick reference: `.claude/skills/logging-helper/reference.md`
- Troubleshooting: `.claude/skills/logging-helper/troubleshooting.md`
---
name: logging
description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes.
---
# Logging
Ensures consistent and efficient logging using `@isa/core/logging` library.
## Core Principles
### 1. Always Use Factory Pattern
```typescript
import { logger } from '@isa/core/logging';
// ✅ DO
#logger = logger();
// ❌ DON'T
constructor(private loggingService: LoggingService) {}
```
### 2. Choose Appropriate Log Levels
- **Trace**: Fine-grained debugging (method entry/exit)
- **Debug**: Development debugging (variable states)
- **Info**: Runtime information (user actions, events)
- **Warn**: Potentially harmful situations
- **Error**: Errors affecting functionality
### 3. Context Patterns
**Static Context** (component level):
```typescript
#logger = logger({ component: 'UserProfileComponent' });
```
**Dynamic Context** (instance level):
```typescript
#logger = logger(() => ({
userId: this.authService.currentUserId,
storeId: this.config.storeId
}));
```
**Message Context** (use functions for performance):
```typescript
// ✅ Recommended - lazy evaluation
this.#logger.info('Order processed', () => ({
orderId: order.id,
total: order.total,
timestamp: Date.now()
}));
// ✅ Acceptable - static values
this.#logger.info('Order processed', {
orderId: order.id,
status: 'completed'
});
```
## Essential Patterns
### Component Logging
```typescript
@Component({
selector: 'app-product-list',
standalone: true,
})
export class ProductListComponent {
#logger = logger({ component: 'ProductListComponent' });
ngOnInit(): void {
this.#logger.info('Component initialized');
}
onAction(id: string): void {
this.#logger.debug('Action triggered', { id });
}
}
```
### Service Logging
```typescript
@Injectable({ providedIn: 'root' })
export class DataService {
#logger = logger({ service: 'DataService' });
fetchData(endpoint: string): Observable<Data> {
this.#logger.debug('Fetching data', { endpoint });
return this.http.get<Data>(endpoint).pipe(
tap((data) => this.#logger.info('Data fetched', () => ({
endpoint,
size: data.length
}))),
catchError((error) => {
this.#logger.error('Fetch failed', error, () => ({
endpoint,
status: error.status
}));
return throwError(() => error);
})
);
}
}
```
### Error Handling
```typescript
try {
await this.processOrder(orderId);
} catch (error) {
this.#logger.error('Order processing failed', error as Error, () => ({
orderId,
step: this.currentStep,
attemptNumber: this.retryCount
}));
throw error;
}
```
### Hierarchical Context
```typescript
@Component({
providers: [
provideLoggerContext({ feature: 'checkout', module: 'sales' })
]
})
export class CheckoutComponent {
#logger = logger(() => ({ userId: this.userService.currentUserId }));
// Logs include: feature, module, userId + message context
}
```
## Common Mistakes to Avoid
```typescript
// ❌ Don't use console.log
console.log('User logged in');
// ✅ Use logger
this.#logger.info('User logged in');
// ❌ Don't create expensive context eagerly
this.#logger.debug('Processing', {
data: this.computeExpensive() // Always executes
});
// ✅ Use function for lazy evaluation
this.#logger.debug('Processing', () => ({
data: this.computeExpensive() // Only if debug enabled
}));
// ❌ Don't log in tight loops
for (const item of items) {
this.#logger.debug('Item', { item });
}
// ✅ Log aggregates
this.#logger.debug('Batch processed', () => ({
count: items.length
}));
// ❌ Don't log sensitive data
this.#logger.info('User auth', { password: user.password });
// ✅ Log safe identifiers only
this.#logger.info('User auth', { userId: user.id });
// ❌ Don't miss error object
this.#logger.error('Failed');
// ✅ Include error object
this.#logger.error('Failed', error as Error);
```
## Configuration
### App Configuration
```typescript
// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import {
provideLogging, withLogLevel, withSink,
LogLevel, ConsoleLogSink
} from '@isa/core/logging';
export const appConfig: ApplicationConfig = {
providers: [
provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
withSink(ConsoleLogSink),
withContext({ app: 'ISA', version: '1.0.0' })
)
]
};
```
## Testing
```typescript
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { LoggingService } from '@isa/core/logging';
describe('MyComponent', () => {
const createComponent = createComponentFactory({
component: MyComponent,
mocks: [LoggingService]
});
it('should log error', () => {
const spectator = createComponent();
const loggingService = spectator.inject(LoggingService);
spectator.component.riskyOperation();
expect(loggingService.error).toHaveBeenCalledWith(
expect.any(String),
expect.any(Error),
expect.any(Function)
);
});
});
```
## Additional Resources
**For more detailed information:**
- **API signatures and patterns**: See [references/api-reference.md](references/api-reference.md) for complete API documentation
- **Real-world examples**: See [references/examples.md](references/examples.md) for components, services, guards, interceptors, and more
- **Troubleshooting**: See [references/troubleshooting.md](references/troubleshooting.md) for common issues and solutions
**Project documentation:**
- Full library documentation: `libs/core/logging/README.md`

View File

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

View File

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

View File

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

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.
## When to Use This Skill
Invoke this skill when:
- **After** checking `libs/ui/**` for existing components (always check first!)
- Styling layout and spacing for components
- Choosing appropriate color values for custom elements
- Applying typography classes to text content
- Determining spacing, layout, or responsive breakpoints
- Customizing or extending existing UI components
- Ensuring design system consistency
- Questions about which Tailwind utility classes are available
**Important**: This skill provides Tailwind utilities. Always prefer using components from `@isa/ui/*` libraries before applying custom Tailwind styles.
**Works together with:**
- **[template-standards](../template-standards/SKILL.md)** - Angular template syntax, E2E testing attributes, and ARIA accessibility
- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files
When building Angular components, these skills work together:
1. Use **template-standards** for Angular syntax, `data-*`, and ARIA attributes
2. Use **tailwind** (this skill) for styling with the ISA design system
3. Use **logging** for all component/service logging
## Core Design System Principles
### 0. Component Libraries First (Most Important)

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.
## When to Use
- Creating or reviewing component templates
- Refactoring legacy `*ngIf/*ngFor/*ngSwitch` to modern syntax
- Implementing `@defer` lazy loading
- Designing reusable components with `ng-content`
- Adding E2E testing attributes for automated tests
- Ensuring WCAG accessibility compliance
- Template performance optimization
**Related Skills:**
- **[tailwind](../tailwind/SKILL.md)** - ISA design system styling (colors, typography, spacing, layout)
- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files using `@isa/core/logging`
## Overview
This skill combines three essential aspects of Angular template development:
@@ -31,7 +17,9 @@ This skill combines three essential aspects of Angular template development:
**Every interactive element MUST include both E2E and ARIA attributes.**
---
**Related Skills:**
- **tailwind** - ISA design system styling (colors, typography, spacing, layout)
- **logging** - MANDATORY logging in all Angular files using `@isa/core/logging`
## Part 1: Angular Template Syntax
@@ -81,6 +69,8 @@ This skill combines three essential aspects of Angular template development:
}
```
**See [control-flow-reference.md](references/control-flow-reference.md) for advanced patterns including nested loops, complex conditions, and filter strategies.**
### @defer Lazy Loading
#### Basic Usage
@@ -97,7 +87,7 @@ This skill combines three essential aspects of Angular template development:
}
```
#### Triggers
#### Common Triggers
| Trigger | Use Case |
|---------|----------|
@@ -111,18 +101,15 @@ This skill combines three essential aspects of Angular template development:
**Multiple triggers:** `@defer (on interaction; on timer(5s))`
**Prefetching:** `@defer (on interaction; prefetch on idle)`
#### Requirements
#### Critical Requirements
- Components **MUST be standalone**
- No `@ViewChild`/`@ContentChild` references
- Reserve space in `@placeholder` to prevent layout shift
- Never defer above-the-fold content (harms LCP)
- Avoid `immediate`/`timer` during initial render (harms TTI)
#### Best Practices
- ✅ Defer below-the-fold content
- ❌ Never defer above-the-fold (harms LCP)
- ❌ Avoid `immediate`/`timer` during initial render (harms TTI)
- Test with network throttling
**See [defer-patterns.md](references/defer-patterns.md) for performance optimization, Core Web Vitals impact, bundle size reduction strategies, and real-world examples.**
### Content Projection
@@ -156,12 +143,9 @@ This skill combines three essential aspects of Angular template development:
</ui-card>
```
**Fallback content:** `<ng-content select="title">Default Title</ng-content>`
**Aliasing:** `<h3 ngProjectAs="card-header">Title</h3>`
**CRITICAL Constraint:** `ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
#### CRITICAL Constraint
`ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
**See [projection-patterns.md](references/projection-patterns.md) for conditional projection, template-based projection, querying projected content, and modal/form field examples.**
### Template References
@@ -193,6 +177,8 @@ Groups elements without DOM footprint:
</p>
```
**See [template-reference.md](references/template-reference.md) for programmatic rendering, ViewContainerRef patterns, context scoping, and common pitfalls.**
### Variables
#### @let (Angular 18.1+)
@@ -226,16 +212,14 @@ Groups elements without DOM footprint:
**Class:** `[class.active]="isActive()"` or `[class]="{active: isActive()}"`
**Style:** `[style.width.px]="width()"` or `[style]="{color: textColor()}"`
---
## Part 2: E2E Testing Attributes
### Purpose
Enable automated end-to-end testing by providing stable selectors for QA automation:
- **`data-what`**: Semantic description of element's purpose
- **`data-which`**: Unique identifier for specific instances
- **`data-*`**: Additional contextual information
- **`data-what`**: Semantic description of element's purpose (e.g., `submit-button`, `email-input`)
- **`data-which`**: Unique identifier for specific instances (e.g., `registration-form`, `customer-123`)
- **`data-*`**: Additional contextual information (e.g., `data-status="active"`)
### Naming Conventions
@@ -259,9 +243,8 @@ Enable automated end-to-end testing by providing stable selectors for QA automat
3. ✅ Ensure `data-which` is unique within the view
4. ✅ Use Angular binding for dynamic values: `[attr.data-*]`
5. ✅ Avoid including sensitive data in attributes
6. ✅ Document complex attribute patterns in template comments
---
**See [e2e-attributes.md](references/e2e-attributes.md) for complete patterns by element type (buttons, inputs, links, lists, tables, dialogs), dynamic attribute bindings, testing integration examples, and validation strategies.**
## Part 3: ARIA Accessibility Attributes
@@ -290,7 +273,7 @@ Ensure web applications are accessible to all users, including those using assis
6. ✅ Keep ARIA attributes in sync with visual states
7. ✅ Test with screen readers (NVDA, JAWS, VoiceOver)
---
**See [aria-attributes.md](references/aria-attributes.md) for comprehensive role reference, property and state attributes, live regions, keyboard navigation patterns, WCAG compliance requirements, and testing strategies.**
## Part 4: Combined Examples
@@ -342,18 +325,6 @@ Ensure web applications are accessible to all users, including those using assis
}
```
### Link with All Attributes
```html
<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
```html
@@ -387,109 +358,7 @@ Ensure web applications are accessible to all users, including those using assis
</div>
```
### Complete Form Example
```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>
}
```
---
**See [combined-patterns.md](references/combined-patterns.md) for complete form examples, product listings, shopping carts, modal dialogs, navigation patterns, data tables, search interfaces, notifications, and multi-step forms with all attributes properly applied.**
## Validation Checklist
@@ -501,7 +370,6 @@ Before considering template complete:
- [ ] Complex expressions moved to `computed()` in component
- [ ] @defer used for below-the-fold or heavy components
- [ ] Content projection using ng-content with proper selectors (if applicable)
- [ ] Template references using # for local variables
- [ ] Using @let for template-scoped variables (Angular 18.1+)
### E2E Attributes
@@ -510,7 +378,6 @@ Before considering template complete:
- [ ] All links have `data-what` and `data-which`
- [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers
- [ ] No duplicate `data-which` values within the same view
- [ ] Additional `data-*` attributes for contextual information (if needed)
### ARIA Accessibility
- [ ] All interactive elements have appropriate ARIA labels
@@ -520,16 +387,13 @@ Before considering template complete:
- [ ] Dialogs have `role="dialog"`, `aria-modal`, and label relationships
- [ ] Dynamic state changes reflected in ARIA attributes
- [ ] Keyboard accessibility (tabindex, enter/space handlers where needed)
- [ ] Screen reader testing completed (if applicable)
### Combined Standards
- [ ] Every interactive element has BOTH E2E and ARIA attributes
- [ ] Attributes organized logically in template (Angular directives → data-* → aria-*)
- [ ] Attributes organized logically (Angular directives → data-* → aria-*)
- [ ] Dynamic bindings use `[attr.*]` syntax correctly
- [ ] No accessibility violations (semantic HTML preferred over ARIA)
---
## Migration Guide
### From Legacy Angular Syntax
@@ -549,31 +413,27 @@ Before considering template complete:
3. **Add ARIA attributes**: appropriate role, label, and state attributes
4. **Test**: verify selectors work in E2E tests, validate with screen readers
---
## Reference Files
For detailed examples and advanced patterns, see:
### Angular Syntax References
- `references/control-flow-reference.md` - @if/@for/@switch patterns
- `references/defer-patterns.md` - Lazy loading strategies
- `references/projection-patterns.md` - Advanced ng-content
- `references/template-reference.md` - ng-template/ng-container
- `references/control-flow-reference.md` - Advanced @if/@for/@switch patterns, nested loops, filtering
- `references/defer-patterns.md` - Lazy loading strategies, Core Web Vitals, performance optimization
- `references/projection-patterns.md` - Advanced ng-content, conditional projection, template-based patterns
- `references/template-reference.md` - ng-template/ng-container, programmatic rendering, ViewContainerRef
### E2E Testing References
- `references/e2e-attributes.md` - Complete E2E attribute patterns and conventions
- `references/e2e-attributes.md` - Complete E2E attribute patterns, naming conventions, testing integration
### ARIA Accessibility References
- `references/aria-attributes.md` - Comprehensive ARIA guidance and WCAG compliance
- `references/aria-attributes.md` - Comprehensive ARIA guidance, roles, properties, states, WCAG compliance
### Combined References
- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA
- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA integrated
Search with: `grep -r "pattern" references/`
---
## Quick Reference Summary
**Every interactive element needs:**

View File

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