@smartsoft001-mobilems/claude-plugins 2.67.0 → 2.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/flow/.claude-plugin/plugin.json +1 -1
- package/plugins/flow-legacy/.claude-plugin/README.md +143 -0
- package/plugins/flow-legacy/.claude-plugin/merge-permissions.js +80 -0
- package/plugins/flow-legacy/.claude-plugin/plugin.json +5 -0
- package/plugins/flow-legacy/.claude-plugin/settings.template.json +75 -0
- package/plugins/flow-legacy/agents/angular-component-scaffolder.md +323 -0
- package/plugins/flow-legacy/agents/angular-directive-builder.md +258 -0
- package/plugins/flow-legacy/agents/angular-guard-builder.md +322 -0
- package/plugins/flow-legacy/agents/angular-pipe-builder.md +227 -0
- package/plugins/flow-legacy/agents/angular-resolver-builder.md +332 -0
- package/plugins/flow-legacy/agents/angular-service-builder.md +271 -0
- package/plugins/flow-legacy/agents/angular-state-builder.md +473 -0
- package/plugins/flow-legacy/agents/shared-impl-orchestrator.md +161 -0
- package/plugins/flow-legacy/agents/shared-impl-reporter.md +204 -0
- package/plugins/flow-legacy/agents/shared-linear-subtask-iterator.md +187 -0
- package/plugins/flow-legacy/agents/shared-tdd-developer.md +304 -0
- package/plugins/flow-legacy/agents/shared-test-runner.md +131 -0
- package/plugins/flow-legacy/agents/shared-ui-classifier.md +137 -0
- package/plugins/flow-legacy/commands/commit.md +162 -0
- package/plugins/flow-legacy/commands/impl.md +495 -0
- package/plugins/flow-legacy/commands/plan.md +488 -0
- package/plugins/flow-legacy/commands/push.md +470 -0
- package/plugins/flow-legacy/skills/a11y-audit/SKILL.md +214 -0
- package/plugins/flow-legacy/skills/angular-patterns/SKILL.md +361 -0
- package/plugins/flow-legacy/skills/browser-capture/SKILL.md +238 -0
- package/plugins/flow-legacy/skills/debug-helper/SKILL.md +387 -0
- package/plugins/flow-legacy/skills/linear-suggestion/SKILL.md +132 -0
- package/plugins/flow-legacy/skills/maia-files-delete/SKILL.md +59 -0
- package/plugins/flow-legacy/skills/maia-files-upload/SKILL.md +57 -0
- package/plugins/flow-legacy/skills/nx-conventions/SKILL.md +371 -0
- package/plugins/flow-legacy/skills/test-unit/SKILL.md +494 -0
package/package.json
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Flow-Legacy Plugin for Claude Code
|
|
2
|
+
|
|
3
|
+
Development flow commands for **legacy Angular 14 projects** with Linear-driven workflow.
|
|
4
|
+
|
|
5
|
+
## Target Projects
|
|
6
|
+
|
|
7
|
+
This plugin is designed for older MobileMS projects using:
|
|
8
|
+
|
|
9
|
+
- **Angular 14.x** (not Angular 20+)
|
|
10
|
+
- **Nx 14.x** with `@nrwl/*` packages
|
|
11
|
+
- **NgRx 14.x**
|
|
12
|
+
- **Node.js 18** (not 24)
|
|
13
|
+
|
|
14
|
+
### Legacy Patterns (what this plugin supports)
|
|
15
|
+
|
|
16
|
+
| Feature | Legacy (Angular 14) | Modern (Angular 20+) |
|
|
17
|
+
|---------|---------------------|----------------------|
|
|
18
|
+
| Control flow | `*ngIf`, `*ngFor` | `@if`, `@for` |
|
|
19
|
+
| Dependency injection | Constructor injection | `inject()` |
|
|
20
|
+
| Inputs/Outputs | `@Input()`, `@Output()` | `input()`, `output()` |
|
|
21
|
+
| State management | BehaviorSubject, Observable | Signals |
|
|
22
|
+
| Components | NgModules | Standalone |
|
|
23
|
+
| Nx packages | `@nrwl/*` | `@nx/*` |
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `/commit` | Create conventional commit based on Linear task |
|
|
30
|
+
| `/impl` | Implement plans from Linear task comments |
|
|
31
|
+
| `/plan` | Create implementation plan and save to Linear |
|
|
32
|
+
| `/push` | Push changes and update Linear status |
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### 1. Enable plugin in project's `.claude/settings.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"enabledPlugins": {
|
|
41
|
+
"flow-legacy@mobilems": true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Merge permissions from template
|
|
47
|
+
|
|
48
|
+
Run the merge script:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node node_modules/@smartsoft001-mobilems/claude-plugins/plugins/flow-legacy/.claude-plugin/merge-permissions.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or manually copy permissions from `settings.template.json` to your project's `.claude/settings.json`.
|
|
55
|
+
|
|
56
|
+
## Required MCP Servers
|
|
57
|
+
|
|
58
|
+
Configure these in your `.mcp.json`:
|
|
59
|
+
|
|
60
|
+
- `linear-server` - Linear API integration
|
|
61
|
+
- `playwright` - Browser automation for screenshots
|
|
62
|
+
|
|
63
|
+
## Permission Strategy
|
|
64
|
+
|
|
65
|
+
### Automatic (allow)
|
|
66
|
+
- Git read operations (status, diff, log, show)
|
|
67
|
+
- Nx commands (test, lint, build)
|
|
68
|
+
- Linear API (read/write issues, comments)
|
|
69
|
+
- Playwright (screenshots)
|
|
70
|
+
- Internal skills
|
|
71
|
+
|
|
72
|
+
### Requires Confirmation (ask)
|
|
73
|
+
- Flow commands (/commit, /impl, /plan, /push)
|
|
74
|
+
- `git commit` - creating commits
|
|
75
|
+
- `git push` - pushing to remote
|
|
76
|
+
- `gh pr create` - creating pull requests
|
|
77
|
+
|
|
78
|
+
## Skills
|
|
79
|
+
|
|
80
|
+
| Skill | Description |
|
|
81
|
+
|-------|-------------|
|
|
82
|
+
| `maia-files-upload` | Upload files to Maia API |
|
|
83
|
+
| `maia-files-delete` | Delete files from Maia API |
|
|
84
|
+
| `browser-capture` | Capture screenshots with Playwright |
|
|
85
|
+
| `linear-suggestion` | Create Linear issues for improvements |
|
|
86
|
+
| `test-unit` | Run unit tests with coverage |
|
|
87
|
+
| `a11y-audit` | Accessibility audit |
|
|
88
|
+
|
|
89
|
+
## Agents
|
|
90
|
+
|
|
91
|
+
See `agents/` directory for specialized agents adapted for Angular 14 patterns:
|
|
92
|
+
|
|
93
|
+
### Angular Agents (Legacy patterns)
|
|
94
|
+
- `angular-component-scaffolder` - Components with NgModules, `*ngIf`/`*ngFor`, constructor DI
|
|
95
|
+
- `angular-service-builder` - Services with BehaviorSubject, constructor DI
|
|
96
|
+
- `angular-state-builder` - State management with BehaviorSubject/Observable patterns
|
|
97
|
+
- `angular-directive-builder` - Directives with constructor DI
|
|
98
|
+
- `angular-pipe-builder` - Pipes with legacy patterns
|
|
99
|
+
- `angular-guard-builder` - Guards with constructor DI
|
|
100
|
+
- `angular-resolver-builder` - Resolvers with constructor DI
|
|
101
|
+
|
|
102
|
+
### Shared Agents
|
|
103
|
+
- `shared-tdd-developer` - TDD workflow
|
|
104
|
+
- `shared-impl-orchestrator` - Implementation orchestration
|
|
105
|
+
- `shared-impl-reporter` - Implementation reporting
|
|
106
|
+
- `shared-linear-subtask-iterator` - Linear subtask iteration
|
|
107
|
+
- `shared-verification-orchestrator` - Verification pipeline
|
|
108
|
+
- And more...
|
|
109
|
+
|
|
110
|
+
## Differences from `flow` plugin
|
|
111
|
+
|
|
112
|
+
| Aspect | `flow` (modern) | `flow-legacy` |
|
|
113
|
+
|--------|-----------------|---------------|
|
|
114
|
+
| Angular version | 20+ | 14 |
|
|
115
|
+
| Node.js version | 24 | 18 |
|
|
116
|
+
| Component syntax | Standalone, signals | NgModules, *ngIf/*ngFor |
|
|
117
|
+
| DI pattern | `inject()` | Constructor injection |
|
|
118
|
+
| Nx packages | `@nx/*` | `@nrwl/*` |
|
|
119
|
+
| State | Signals | BehaviorSubject/Observable |
|
|
120
|
+
|
|
121
|
+
## Example Project Structure
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
libs/
|
|
125
|
+
shared/
|
|
126
|
+
angular/
|
|
127
|
+
src/
|
|
128
|
+
lib/
|
|
129
|
+
components/
|
|
130
|
+
services/
|
|
131
|
+
pipes/
|
|
132
|
+
directives/
|
|
133
|
+
museum-articles/
|
|
134
|
+
shell/
|
|
135
|
+
angular/
|
|
136
|
+
museum-objects/
|
|
137
|
+
shell/
|
|
138
|
+
angular/
|
|
139
|
+
apps/
|
|
140
|
+
web/
|
|
141
|
+
src/
|
|
142
|
+
app/
|
|
143
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Merge flow-legacy plugin permissions into project's .claude/settings.json
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @smartsoft001-mobilems/claude-plugins flow-legacy:merge-permissions
|
|
7
|
+
*
|
|
8
|
+
* Or directly:
|
|
9
|
+
* node node_modules/@smartsoft001-mobilems/claude-plugins/plugins/flow-legacy/.claude-plugin/merge-permissions.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const SETTINGS_PATH = path.join(process.cwd(), '.claude', 'settings.json');
|
|
16
|
+
const TEMPLATE_PATH = path.join(__dirname, 'settings.template.json');
|
|
17
|
+
|
|
18
|
+
function loadJson(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mergeArrays(existing, template) {
|
|
27
|
+
return [...new Set([...existing, ...template])];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function main() {
|
|
31
|
+
console.log('Flow-Legacy Plugin - Merge Permissions\n');
|
|
32
|
+
|
|
33
|
+
// Load template
|
|
34
|
+
const template = loadJson(TEMPLATE_PATH);
|
|
35
|
+
if (!template) {
|
|
36
|
+
console.error('ERROR: Could not load template from:', TEMPLATE_PATH);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Load or create settings
|
|
41
|
+
let settings = loadJson(SETTINGS_PATH);
|
|
42
|
+
if (!settings) {
|
|
43
|
+
console.log('Creating new .claude/settings.json...');
|
|
44
|
+
fs.mkdirSync(path.dirname(SETTINGS_PATH), { recursive: true });
|
|
45
|
+
settings = { permissions: { allow: [], deny: [], ask: [] } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure permissions structure exists
|
|
49
|
+
settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
|
|
50
|
+
settings.permissions.allow = settings.permissions.allow || [];
|
|
51
|
+
settings.permissions.ask = settings.permissions.ask || [];
|
|
52
|
+
settings.permissions.deny = settings.permissions.deny || [];
|
|
53
|
+
|
|
54
|
+
// Count before
|
|
55
|
+
const beforeAllow = settings.permissions.allow.length;
|
|
56
|
+
const beforeAsk = settings.permissions.ask.length;
|
|
57
|
+
|
|
58
|
+
// Merge permissions
|
|
59
|
+
settings.permissions.allow = mergeArrays(settings.permissions.allow, template.permissions.allow);
|
|
60
|
+
settings.permissions.ask = mergeArrays(settings.permissions.ask, template.permissions.ask);
|
|
61
|
+
|
|
62
|
+
// Enable plugin
|
|
63
|
+
settings.enabledPlugins = settings.enabledPlugins || {};
|
|
64
|
+
settings.enabledPlugins['flow-legacy@mobilems'] = true;
|
|
65
|
+
|
|
66
|
+
// Write back
|
|
67
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
68
|
+
|
|
69
|
+
// Report
|
|
70
|
+
const addedAllow = settings.permissions.allow.length - beforeAllow;
|
|
71
|
+
const addedAsk = settings.permissions.ask.length - beforeAsk;
|
|
72
|
+
|
|
73
|
+
console.log('Permissions merged successfully!\n');
|
|
74
|
+
console.log(` allow: ${beforeAllow} -> ${settings.permissions.allow.length} (+${addedAllow})`);
|
|
75
|
+
console.log(` ask: ${beforeAsk} -> ${settings.permissions.ask.length} (+${addedAsk})`);
|
|
76
|
+
console.log(`\nPlugin enabled: flow-legacy@mobilems`);
|
|
77
|
+
console.log(`\nFile updated: ${SETTINGS_PATH}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/anthropics/claude-code/main/schemas/settings.schema.json",
|
|
3
|
+
"_description": "Template permissions for flow-legacy@mobilems plugin. Merge these into your project's .claude/settings.json",
|
|
4
|
+
"permissions": {
|
|
5
|
+
"allow": [
|
|
6
|
+
"Bash(git status:*)",
|
|
7
|
+
"Bash(git diff:*)",
|
|
8
|
+
"Bash(git log:*)",
|
|
9
|
+
"Bash(git show:*)",
|
|
10
|
+
"Bash(git add:*)",
|
|
11
|
+
"Bash(git fetch:*)",
|
|
12
|
+
"Bash(git pull:*)",
|
|
13
|
+
"Bash(gh run list:*)",
|
|
14
|
+
"Bash(gh run view:*)",
|
|
15
|
+
"Bash(gh pr list:*)",
|
|
16
|
+
"Bash(nx:*)",
|
|
17
|
+
"Bash(npx nx:*)",
|
|
18
|
+
"Bash(npx tsc:*)",
|
|
19
|
+
"Bash(npm start)",
|
|
20
|
+
"Bash(npm run format:*)",
|
|
21
|
+
"Bash(npm audit:*)",
|
|
22
|
+
"Bash(npm install:*)",
|
|
23
|
+
"Bash(source:*)",
|
|
24
|
+
"Bash(nvm use:*)",
|
|
25
|
+
"Bash(curl:*)",
|
|
26
|
+
"Bash(lsof:*)",
|
|
27
|
+
"Bash(ls:*)",
|
|
28
|
+
"Bash(find:*)",
|
|
29
|
+
"Bash(grep:*)",
|
|
30
|
+
"Bash(xargs:*)",
|
|
31
|
+
"Bash(sleep:*)",
|
|
32
|
+
"Bash(rm:*)",
|
|
33
|
+
"Bash(test:*)",
|
|
34
|
+
"Bash(for:*)",
|
|
35
|
+
"Bash(node:*)",
|
|
36
|
+
"Bash(mkdir:*)",
|
|
37
|
+
"mcp__linear-server__get_issue",
|
|
38
|
+
"mcp__linear-server__list_issues",
|
|
39
|
+
"mcp__linear-server__list_comments",
|
|
40
|
+
"mcp__linear-server__create_comment",
|
|
41
|
+
"mcp__linear-server__update_issue",
|
|
42
|
+
"mcp__linear-server__list_issue_statuses",
|
|
43
|
+
"mcp__linear-server__create_issue",
|
|
44
|
+
"mcp__playwright__browser_snapshot",
|
|
45
|
+
"mcp__playwright__browser_take_screenshot",
|
|
46
|
+
"mcp__playwright__browser_navigate",
|
|
47
|
+
"mcp__playwright__browser_close",
|
|
48
|
+
"mcp__playwright__browser_wait_for",
|
|
49
|
+
"mcp__playwright__browser_click",
|
|
50
|
+
"mcp__playwright__browser_resize",
|
|
51
|
+
"mcp__playwright__browser_evaluate",
|
|
52
|
+
"mcp__playwright__browser_console_messages",
|
|
53
|
+
"mcp__playwright__browser_press_key",
|
|
54
|
+
"mcp__playwright__browser_network_requests",
|
|
55
|
+
"Skill(flow-legacy:maia-files-upload)",
|
|
56
|
+
"Skill(flow-legacy:maia-files-delete)",
|
|
57
|
+
"Skill(flow-legacy:browser-capture)",
|
|
58
|
+
"Skill(flow-legacy:linear-suggestion)",
|
|
59
|
+
"Skill(flow-legacy:test-unit)",
|
|
60
|
+
"Skill(flow-legacy:a11y-audit)"
|
|
61
|
+
],
|
|
62
|
+
"ask": [
|
|
63
|
+
"Skill(flow-legacy:commit)",
|
|
64
|
+
"Skill(flow-legacy:impl)",
|
|
65
|
+
"Skill(flow-legacy:plan)",
|
|
66
|
+
"Skill(flow-legacy:push)",
|
|
67
|
+
"Bash(git commit:*)",
|
|
68
|
+
"Bash(git push:*)",
|
|
69
|
+
"Bash(gh pr create:*)"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"enabledPlugins": {
|
|
73
|
+
"flow-legacy@mobilems": true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-component-scaffolder
|
|
3
|
+
description: Create Angular 14 components with NgModules and legacy patterns. Use when scaffolding new components following Angular 14 best practices (*ngIf/*ngFor, constructor DI, @Input/@Output).
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: opus
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular 14 components following legacy best practices.
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create Angular components using Angular 14 patterns including NgModules, `*ngIf`/`*ngFor`, constructor injection, and `@Input()`/`@Output()` decorators.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Creating new Angular components in legacy projects (Angular 14)
|
|
18
|
+
- Scaffolding component structure with template and styles
|
|
19
|
+
- Setting up component with proper constructor DI
|
|
20
|
+
|
|
21
|
+
## CRITICAL: Angular 14 Patterns (MANDATORY)
|
|
22
|
+
|
|
23
|
+
**NEVER use modern Angular 20+ patterns. ALWAYS use these legacy patterns:**
|
|
24
|
+
|
|
25
|
+
| Feature | ✅ CORRECT (Angular 14) | ❌ WRONG (Angular 20+) |
|
|
26
|
+
|---------|------------------------|----------------------|
|
|
27
|
+
| Control flow | `*ngIf`, `*ngFor` | `@if`, `@for` |
|
|
28
|
+
| DI | Constructor injection | `inject()` |
|
|
29
|
+
| Inputs | `@Input()` decorator | `input()`, `input.required()` |
|
|
30
|
+
| Outputs | `@Output()` decorator | `output()` |
|
|
31
|
+
| Two-way binding | `@Input()` + `@Output()` | `model()` |
|
|
32
|
+
| State | Class properties | `signal()` |
|
|
33
|
+
| Derived state | Getters or manual | `computed()` |
|
|
34
|
+
|
|
35
|
+
## Project-Specific Patterns
|
|
36
|
+
|
|
37
|
+
This project uses:
|
|
38
|
+
|
|
39
|
+
- **NgModules** - components must be declared in modules
|
|
40
|
+
- **`*ngIf` and `*ngFor`** for control flow
|
|
41
|
+
- **Constructor injection** for DI
|
|
42
|
+
- **`@Input()` and `@Output()`** decorators
|
|
43
|
+
- **No signals** - use class properties and BehaviorSubject
|
|
44
|
+
- **Tailwind CSS** with `smart-` prefix (if applicable)
|
|
45
|
+
|
|
46
|
+
## Component Templates
|
|
47
|
+
|
|
48
|
+
### Basic Component
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
52
|
+
import { Subject } from 'rxjs';
|
|
53
|
+
import { takeUntil } from 'rxjs/operators';
|
|
54
|
+
|
|
55
|
+
@Component({
|
|
56
|
+
selector: 'app-feature-name',
|
|
57
|
+
templateUrl: './feature-name.component.html',
|
|
58
|
+
styleUrls: ['./feature-name.component.scss'],
|
|
59
|
+
})
|
|
60
|
+
export class FeatureNameComponent implements OnInit, OnDestroy {
|
|
61
|
+
// Class properties for state (NOT signals)
|
|
62
|
+
isLoading = false;
|
|
63
|
+
data: DataType[] = [];
|
|
64
|
+
error: string | null = null;
|
|
65
|
+
|
|
66
|
+
// Destroy subject for cleanup
|
|
67
|
+
private readonly destroy$ = new Subject<void>();
|
|
68
|
+
|
|
69
|
+
// Constructor injection (NOT inject())
|
|
70
|
+
constructor(
|
|
71
|
+
private readonly dataService: DataService,
|
|
72
|
+
private readonly router: Router
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
ngOnInit(): void {
|
|
76
|
+
this.loadData();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ngOnDestroy(): void {
|
|
80
|
+
this.destroy$.next();
|
|
81
|
+
this.destroy$.complete();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private loadData(): void {
|
|
85
|
+
this.isLoading = true;
|
|
86
|
+
this.dataService.getData()
|
|
87
|
+
.pipe(takeUntil(this.destroy$))
|
|
88
|
+
.subscribe({
|
|
89
|
+
next: (data) => {
|
|
90
|
+
this.data = data;
|
|
91
|
+
this.isLoading = false;
|
|
92
|
+
},
|
|
93
|
+
error: (err) => {
|
|
94
|
+
this.error = err.message;
|
|
95
|
+
this.isLoading = false;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Getter for derived state (NOT computed())
|
|
101
|
+
get itemCount(): number {
|
|
102
|
+
return this.data.length;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get hasData(): boolean {
|
|
106
|
+
return this.data.length > 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Component with Inputs/Outputs
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
|
115
|
+
|
|
116
|
+
@Component({
|
|
117
|
+
selector: 'app-item-card',
|
|
118
|
+
templateUrl: './item-card.component.html',
|
|
119
|
+
styleUrls: ['./item-card.component.scss'],
|
|
120
|
+
})
|
|
121
|
+
export class ItemCardComponent implements OnChanges {
|
|
122
|
+
// Inputs with @Input() decorator (NOT input())
|
|
123
|
+
@Input() label!: string; // Required input
|
|
124
|
+
@Input() item!: Item; // Required input
|
|
125
|
+
@Input() showActions = true; // Optional with default
|
|
126
|
+
|
|
127
|
+
// Outputs with @Output() decorator (NOT output())
|
|
128
|
+
@Output() selected = new EventEmitter<Item>();
|
|
129
|
+
@Output() closed = new EventEmitter<void>();
|
|
130
|
+
|
|
131
|
+
// Internal state (NOT signals)
|
|
132
|
+
isOpen = false;
|
|
133
|
+
|
|
134
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
135
|
+
if (changes['item']) {
|
|
136
|
+
// React to input changes
|
|
137
|
+
console.log('Item changed:', this.item);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onSelect(): void {
|
|
142
|
+
this.selected.emit(this.item);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onClose(): void {
|
|
146
|
+
this.closed.emit();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
toggleDropdown(force?: boolean): void {
|
|
150
|
+
this.isOpen = force ?? !this.isOpen;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Two-Way Binding Component
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
159
|
+
|
|
160
|
+
@Component({
|
|
161
|
+
selector: 'app-search-input',
|
|
162
|
+
templateUrl: './search-input.component.html',
|
|
163
|
+
})
|
|
164
|
+
export class SearchInputComponent {
|
|
165
|
+
// Two-way binding with @Input + @Output pattern (NOT model())
|
|
166
|
+
@Input() searchText = '';
|
|
167
|
+
@Output() searchTextChange = new EventEmitter<string>();
|
|
168
|
+
|
|
169
|
+
onSearchChange(value: string): void {
|
|
170
|
+
this.searchText = value;
|
|
171
|
+
this.searchTextChange.emit(value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Usage in parent:
|
|
177
|
+
```html
|
|
178
|
+
<app-search-input [(searchText)]="filterText"></app-search-input>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Template Patterns
|
|
182
|
+
|
|
183
|
+
### Control Flow with *ngIf and *ngFor
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<!-- Loading state with *ngIf -->
|
|
187
|
+
<div *ngIf="isLoading" class="loading-spinner">
|
|
188
|
+
<app-spinner></app-spinner>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- Error state with *ngIf -->
|
|
192
|
+
<div *ngIf="error" class="error-message">
|
|
193
|
+
<app-error [message]="error"></app-error>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- Content with *ngIf and *ngFor -->
|
|
197
|
+
<div *ngIf="!isLoading && !error" class="content">
|
|
198
|
+
<ul *ngIf="hasData; else emptyTemplate">
|
|
199
|
+
<li *ngFor="let item of data; trackBy: trackByFn">
|
|
200
|
+
<app-item-card
|
|
201
|
+
[item]="item"
|
|
202
|
+
(selected)="onSelect($event)"
|
|
203
|
+
></app-item-card>
|
|
204
|
+
</li>
|
|
205
|
+
</ul>
|
|
206
|
+
|
|
207
|
+
<ng-template #emptyTemplate>
|
|
208
|
+
<p class="empty-message">
|
|
209
|
+
{{ 'COMMON.noItems' | translate }}
|
|
210
|
+
</p>
|
|
211
|
+
</ng-template>
|
|
212
|
+
</div>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### TrackBy Function
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Always provide trackBy for *ngFor
|
|
219
|
+
trackByFn(index: number, item: Item): string {
|
|
220
|
+
return item.id;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Module Declaration
|
|
225
|
+
|
|
226
|
+
Components MUST be declared in a module:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { NgModule } from '@angular/core';
|
|
230
|
+
import { CommonModule } from '@angular/common';
|
|
231
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
232
|
+
|
|
233
|
+
import { FeatureNameComponent } from './feature-name.component';
|
|
234
|
+
import { ItemCardComponent } from './item-card/item-card.component';
|
|
235
|
+
|
|
236
|
+
@NgModule({
|
|
237
|
+
declarations: [
|
|
238
|
+
FeatureNameComponent,
|
|
239
|
+
ItemCardComponent,
|
|
240
|
+
],
|
|
241
|
+
imports: [
|
|
242
|
+
CommonModule,
|
|
243
|
+
TranslateModule,
|
|
244
|
+
],
|
|
245
|
+
exports: [
|
|
246
|
+
FeatureNameComponent,
|
|
247
|
+
ItemCardComponent,
|
|
248
|
+
],
|
|
249
|
+
})
|
|
250
|
+
export class FeatureModule {}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## File Structure
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
feature-name/
|
|
257
|
+
feature-name.component.ts # Component class
|
|
258
|
+
feature-name.component.html # Template
|
|
259
|
+
feature-name.component.scss # Styles
|
|
260
|
+
feature-name.component.spec.ts # Tests
|
|
261
|
+
feature-name.module.ts # Module (if standalone feature)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Scaffolding Checklist
|
|
265
|
+
|
|
266
|
+
- [ ] Uses NgModule (NOT standalone)
|
|
267
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
268
|
+
- [ ] Uses `@Input()` / `@Output()` decorators (NOT `input()` / `output()`)
|
|
269
|
+
- [ ] Uses `*ngIf` / `*ngFor` in template (NOT `@if` / `@for`)
|
|
270
|
+
- [ ] Has `trackBy` function for all `*ngFor` loops
|
|
271
|
+
- [ ] Uses class properties for state (NOT signals)
|
|
272
|
+
- [ ] Uses getters for derived values (NOT `computed()`)
|
|
273
|
+
- [ ] Has `OnDestroy` with Subject for cleanup
|
|
274
|
+
- [ ] Declared in a module
|
|
275
|
+
- [ ] Imports `TranslateModule` if using translations
|
|
276
|
+
- [ ] Has proper TypeScript types
|
|
277
|
+
|
|
278
|
+
## Output Format
|
|
279
|
+
|
|
280
|
+
```markdown
|
|
281
|
+
## Component Scaffolded
|
|
282
|
+
|
|
283
|
+
### Files Created
|
|
284
|
+
|
|
285
|
+
| File | Purpose |
|
|
286
|
+
| --------------------------------- | ----------------- |
|
|
287
|
+
| `feature-name.component.ts` | Component class |
|
|
288
|
+
| `feature-name.component.html` | Template |
|
|
289
|
+
| `feature-name.component.scss` | Styles |
|
|
290
|
+
| `feature-name.component.spec.ts` | Unit tests |
|
|
291
|
+
|
|
292
|
+
### Component API
|
|
293
|
+
|
|
294
|
+
**Inputs:**
|
|
295
|
+
|
|
296
|
+
- `@Input() item: Item` (required)
|
|
297
|
+
- `@Input() showActions: boolean` (default: true)
|
|
298
|
+
|
|
299
|
+
**Outputs:**
|
|
300
|
+
|
|
301
|
+
- `@Output() selected: EventEmitter<Item>`
|
|
302
|
+
|
|
303
|
+
### Angular 14 Patterns Used
|
|
304
|
+
|
|
305
|
+
- ✅ Constructor injection
|
|
306
|
+
- ✅ `*ngIf` / `*ngFor` control flow
|
|
307
|
+
- ✅ `@Input()` / `@Output()` decorators
|
|
308
|
+
- ✅ Class properties for state
|
|
309
|
+
- ✅ Getters for derived values
|
|
310
|
+
|
|
311
|
+
### Module Registration
|
|
312
|
+
|
|
313
|
+
Add to module declarations:
|
|
314
|
+
```typescript
|
|
315
|
+
declarations: [FeatureNameComponent]
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Next Steps
|
|
319
|
+
|
|
320
|
+
1. Implement component logic
|
|
321
|
+
2. Add to module declarations
|
|
322
|
+
3. Add to routing or parent component
|
|
323
|
+
```
|