@eltonssouza/development-utility-kit 1.0.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/.claude/agents/analyst.md +198 -0
- package/.claude/agents/backend-developer.md +126 -0
- package/.claude/agents/brain-keeper.md +229 -0
- package/.claude/agents/code-reviewer.md +181 -0
- package/.claude/agents/database-engineer.md +94 -0
- package/.claude/agents/devops-engineer.md +141 -0
- package/.claude/agents/frontend-developer.md +97 -0
- package/.claude/agents/gate-keeper.md +118 -0
- package/.claude/agents/migrator.md +291 -0
- package/.claude/agents/mobile-developer.md +80 -0
- package/.claude/agents/n8n-specialist.md +94 -0
- package/.claude/agents/product-owner.md +115 -0
- package/.claude/agents/qa-engineer.md +232 -0
- package/.claude/agents/release-engineer.md +204 -0
- package/.claude/agents/scaffold.md +87 -0
- package/.claude/agents/security-engineer.md +199 -0
- package/.claude/agents/sprint-runner.md +44 -0
- package/.claude/agents/stack-resolver.md +84 -0
- package/.claude/agents/tech-lead.md +182 -0
- package/.claude/agents/update-template.md +54 -0
- package/.claude/agents/ux-designer.md +118 -0
- package/.claude/settings.json +44 -0
- package/.claude/skills/README.md +332 -0
- package/.claude/skills/active-project/SKILL.md +129 -0
- package/.claude/skills/api-integration-test/SKILL.md +64 -0
- package/.claude/skills/auto-test-guard/SKILL.md +237 -0
- package/.claude/skills/auto-test-guard/resources/backend-tests.md +20 -0
- package/.claude/skills/auto-test-guard/resources/e2e-tests.md +24 -0
- package/.claude/skills/auto-test-guard/resources/execution-report.md +49 -0
- package/.claude/skills/auto-test-guard/resources/frontend-tests.md +18 -0
- package/.claude/skills/auto-test-guard/resources/initial-setup.md +108 -0
- package/.claude/skills/auto-test-guard/resources/run-suite.md +48 -0
- package/.claude/skills/auto-test-guard/resources/senior-gate.md +19 -0
- package/.claude/skills/brain-keeper/SKILL.md +60 -0
- package/.claude/skills/brain-keeper/obsidian/app.json +9 -0
- package/.claude/skills/brain-keeper/obsidian/appearance.json +4 -0
- package/.claude/skills/brain-keeper/obsidian/core-plugins.json +20 -0
- package/.claude/skills/brain-keeper/obsidian/daily-notes.json +5 -0
- package/.claude/skills/brain-keeper/obsidian/graph.json +32 -0
- package/.claude/skills/brain-keeper/obsidian/snippets/folder-colors.css +90 -0
- package/.claude/skills/brain-keeper/obsidian/templates.json +5 -0
- package/.claude/skills/brain-keeper/templates/README.md +51 -0
- package/.claude/skills/brain-keeper/templates/adr.md +40 -0
- package/.claude/skills/brain-keeper/templates/bug.md +35 -0
- package/.claude/skills/brain-keeper/templates/daily.md +38 -0
- package/.claude/skills/brain-keeper/templates/feature.md +62 -0
- package/.claude/skills/brain-keeper/templates/meeting.md +34 -0
- package/.claude/skills/brain-keeper/templates/tech-debt.md +21 -0
- package/.claude/skills/caveman/SKILL.md +187 -0
- package/.claude/skills/create-stack-pack/SKILL.md +281 -0
- package/.claude/skills/grill-me/SKILL.md +79 -0
- package/.claude/skills/honcho-memory/SKILL.md +207 -0
- package/.claude/skills/honcho-memory/docs/api-endpoints-verified.md +75 -0
- package/.claude/skills/honcho-memory/hooks/on-prompt-submit.js +221 -0
- package/.claude/skills/honcho-memory/hooks/on-stop.js +193 -0
- package/.claude/skills/honcho-memory/lib/honcho-client.js +363 -0
- package/.claude/skills/honcho-memory/lib/memory-injector.js +93 -0
- package/.claude/skills/honcho-memory/package.json +32 -0
- package/.claude/skills/honcho-memory/scripts/cli.js +370 -0
- package/.claude/skills/honcho-memory/scripts/setup.js +109 -0
- package/.claude/skills/honcho-memory/tests/t001-api-endpoints-verified.test.js +89 -0
- package/.claude/skills/honcho-memory/tests/t002-structure.test.js +97 -0
- package/.claude/skills/honcho-memory/tests/t003-honcho-client.test.js +162 -0
- package/.claude/skills/honcho-memory/tests/t004-soft-delete.test.js +259 -0
- package/.claude/skills/honcho-memory/tests/t005-memory-injector.test.js +175 -0
- package/.claude/skills/honcho-memory/tests/t006-on-prompt-submit.test.js +215 -0
- package/.claude/skills/honcho-memory/tests/t007-on-stop.test.js +165 -0
- package/.claude/skills/honcho-memory/tests/t008-cli.test.js +214 -0
- package/.claude/skills/honcho-memory/tests/t009-setup.test.js +232 -0
- package/.claude/skills/honcho-memory/tests/t010-skill-md.test.js +114 -0
- package/.claude/skills/honcho-memory/tests/t011-settings-hooks.test.js +105 -0
- package/.claude/skills/honcho-memory/tests/t012-docs-update.test.js +106 -0
- package/.claude/skills/honcho-memory/tests/t013-smoke-e2e.test.js +90 -0
- package/.claude/skills/pair-debug/SKILL.md +288 -0
- package/.claude/skills/prd-ready-check/SKILL.md +58 -0
- package/.claude/skills/project-manager/SKILL.md +167 -0
- package/.claude/skills/quality-standards/SKILL.md +201 -0
- package/.claude/skills/quick-feature/SKILL.md +264 -0
- package/.claude/skills/run-sprint/SKILL.md +342 -0
- package/.claude/skills/scaffold/SKILL.md +58 -0
- package/.claude/skills/stack-discovery/SKILL.md +159 -0
- package/.claude/skills/test-coverage-auditor/SKILL.md +59 -0
- package/.claude/skills/to-issues/SKILL.md +163 -0
- package/.claude/skills/to-prd/SKILL.md +130 -0
- package/.claude/skills/update-template/SKILL.md +254 -0
- package/.claude/stacks/CODEOWNERS +30 -0
- package/.claude/stacks/README.md +88 -0
- package/.claude/stacks/_template.md +116 -0
- package/.claude/stacks/java/spring-boot-3.md +376 -0
- package/.claude/stacks/java/spring-boot-4.md +438 -0
- package/.claude/stacks/typescript/angular-18.md +420 -0
- package/.claude/stacks/typescript/angular-19.md +397 -0
- package/.claude/stacks/typescript/angular-21.md +494 -0
- package/CLAUDE.md +453 -0
- package/README.md +391 -0
- package/bin/cli.js +773 -0
- package/bin/lib/backup.js +62 -0
- package/bin/lib/detect-stack.js +476 -0
- package/bin/lib/help.js +233 -0
- package/bin/lib/identity.js +108 -0
- package/bin/lib/local-dir.js +69 -0
- package/bin/lib/manifest.js +236 -0
- package/bin/lib/sync-all.js +394 -0
- package/bin/lib/version-check.js +398 -0
- package/dashboard/db.js +199 -0
- package/dashboard/package.json +22 -0
- package/dashboard/public/app.js +709 -0
- package/dashboard/public/content/docs/agents-reference.en.md +911 -0
- package/dashboard/public/content/docs/architecture-overview.en.md +260 -0
- package/dashboard/public/content/docs/autonomy-matrix.en.md +186 -0
- package/dashboard/public/content/docs/git-flow.en.md +525 -0
- package/dashboard/public/content/docs/honcho-memory.en.md +394 -0
- package/dashboard/public/content/docs/hooks-reference.en.md +420 -0
- package/dashboard/public/content/docs/pipeline.en.md +400 -0
- package/dashboard/public/content/docs/quality-gate.en.md +315 -0
- package/dashboard/public/content/docs/skills-reference.en.md +500 -0
- package/dashboard/public/content/docs/stack-rules.en.md +362 -0
- package/dashboard/public/content/docs/troubleshooting.en.md +637 -0
- package/dashboard/public/content/manifest.json +102 -0
- package/dashboard/public/content/manual/backend.en.md +1138 -0
- package/dashboard/public/content/manual/existing-project.en.md +831 -0
- package/dashboard/public/content/manual/frontend.en.md +1065 -0
- package/dashboard/public/content/manual/fullstack.en.md +1508 -0
- package/dashboard/public/content/manual/mobile.en.md +866 -0
- package/dashboard/public/index.html +108 -0
- package/dashboard/public/style.css +610 -0
- package/dashboard/public/vendor/marked.min.js +69 -0
- package/dashboard/rtk.js +143 -0
- package/dashboard/server-app.js +403 -0
- package/dashboard/server.js +104 -0
- package/dashboard/test/sprint1.test.js +406 -0
- package/dashboard/test/sprint2.test.js +571 -0
- package/dashboard/test/sprint3.test.js +560 -0
- package/package.json +33 -0
- package/scripts/hooks/subagent-telemetry.sh +14 -0
- package/scripts/hooks/telemetry-writer.js +250 -0
- package/scripts/latest-versions.json +56 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
---
|
|
2
|
+
stack: typescript/angular-21
|
|
3
|
+
versions_covered: "21.0.x — 21.x"
|
|
4
|
+
last_validated: 2026-05-27
|
|
5
|
+
validated_against: "sandbox — Angular 21.1.0 (current greenfield)"
|
|
6
|
+
status: active
|
|
7
|
+
pack_owner: "@elton"
|
|
8
|
+
security_review: 2026-05-27
|
|
9
|
+
next_review_due: 2027-05-27
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Angular 21 (TypeScript) — greenfield default
|
|
13
|
+
|
|
14
|
+
Knowledge pack for greenfield Angular 21 projects. Default pack for any new frontend started on or after this pack's `last_validated` date. Covers zoneless apps with Signal Forms, Resource API, Server Components, and `@defer` blocks as first-class primitives. For maintained legacy projects on Angular 18 / 19, use the respective packs.
|
|
15
|
+
|
|
16
|
+
## 1. When to use this pack
|
|
17
|
+
|
|
18
|
+
- Project declares `Primary stack: Angular 21.x` (or `Frontend stack: Angular 21.x`) in `## Project Identity`.
|
|
19
|
+
- `package.json` declares `"@angular/core": "^21.0.0"` (or higher within 21.x).
|
|
20
|
+
- **DEFAULT for NEW projects (greenfield)** — `scaffold` agent selects this pack unless project explicitly pins an older major.
|
|
21
|
+
- For Angular 18.x or 19.x projects in maintenance, use `angular-18.md` / `angular-19.md`. Do NOT use this pack to backport 21-only APIs (Signal Forms stable, Resource API stable, Server Components) onto those versions.
|
|
22
|
+
|
|
23
|
+
## 2. Stack baseline (what this pack assumes)
|
|
24
|
+
|
|
25
|
+
| Component | Version range | Notes |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Angular | 21.0.x — 21.x | Current LTS at pack creation (release ~nov/2025). Standalone is the only supported mode (NgModule deprecated). |
|
|
28
|
+
| Node.js | 22.x LTS | Required by Angular 21 toolchain. Node 20 EOL during 21.x lifetime. |
|
|
29
|
+
| TypeScript | 5.6+ | Strict mode mandatory (`"strict": true`, `"noUncheckedIndexedAccess": true`). |
|
|
30
|
+
| RxJS | 7.8.x | OPTIONAL — Resource API + Signals cover most cases. Keep for HTTP streams and event buses. |
|
|
31
|
+
| UI library | Angular Material 21 (preferred greenfield) OR ng-bootstrap 19.x | Material aligns with M3 tokens and signal-based APIs in 21. |
|
|
32
|
+
| Test framework | Jest 30.x + jest-preset-angular 14 | Karma is removed from `ng new` since 18; Jest is the canonical choice. |
|
|
33
|
+
| Component testing | @testing-library/angular 18 | Signal-aware; works with `resource()` mocks. |
|
|
34
|
+
| E2E | Playwright 1.52+ | `@playwright/test`. Replaces Protractor permanently. |
|
|
35
|
+
| A11y testing | jest-axe 9.x + @axe-core/playwright 4.12+ | Per ADR-007 senior+ gate. |
|
|
36
|
+
| Perf gate | @lhci/cli (Lighthouse CI) | Per ADR-007 thresholds. |
|
|
37
|
+
| Builder | `application` (esbuild) | Default since 18, mandatory in 21. `browser` (webpack) builder removed. |
|
|
38
|
+
| Change detection | **Zoneless** | Default for greenfield. `provideZonelessChangeDetection()` replaces `provideZoneChangeDetection()`. Zone.js NOT installed. |
|
|
39
|
+
|
|
40
|
+
### Notable maturity vs Angular 19
|
|
41
|
+
|
|
42
|
+
- **Signal Forms STABLE** — preferred over Reactive Forms for greenfield work.
|
|
43
|
+
- **Resource API STABLE** — primary mechanism for declarative data fetching.
|
|
44
|
+
- **`effect()` STABLE** — fully supported outside experimental flag.
|
|
45
|
+
- **`output()` function is default** — `@Output` decorator deprecated; lint warns on use.
|
|
46
|
+
- **Server Components STABLE** — SSR / hybrid rendering first-class.
|
|
47
|
+
- **`linkedSignal()` mature** — derived state with explicit dependency tracking.
|
|
48
|
+
- **`@defer` enriched** — supports `on viewport`, `on interaction`, `on idle`, `on hover`, `on timer`, `prefetch` triggers.
|
|
49
|
+
- **Standalone-only** — NgModule path deprecated; new code rejects NgModule in code review.
|
|
50
|
+
|
|
51
|
+
## 3. Project structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
frontend/
|
|
55
|
+
├── src/
|
|
56
|
+
│ ├── app/
|
|
57
|
+
│ │ ├── core/ ← auth, http interceptors, guards, singletons
|
|
58
|
+
│ │ │ ├── auth/
|
|
59
|
+
│ │ │ ├── interceptors/
|
|
60
|
+
│ │ │ └── guards/
|
|
61
|
+
│ │ ├── shared/ ← reusable components, pipes, directives
|
|
62
|
+
│ │ │ ├── components/
|
|
63
|
+
│ │ │ └── pipes/
|
|
64
|
+
│ │ ├── features/ ← lazy-loaded feature areas
|
|
65
|
+
│ │ │ ├── products/
|
|
66
|
+
│ │ │ │ ├── product-list.component.ts
|
|
67
|
+
│ │ │ │ ├── product-list.component.html
|
|
68
|
+
│ │ │ │ ├── product-list.component.scss
|
|
69
|
+
│ │ │ │ ├── product-form.component.ts
|
|
70
|
+
│ │ │ │ ├── product-form.component.html
|
|
71
|
+
│ │ │ │ ├── product-form.component.scss
|
|
72
|
+
│ │ │ │ └── product.service.ts
|
|
73
|
+
│ │ │ └── home/
|
|
74
|
+
│ │ ├── app.config.ts ← bootstrap providers (zoneless, router, http, etc.)
|
|
75
|
+
│ │ ├── app.routes.ts ← root routes (lazy via loadComponent)
|
|
76
|
+
│ │ └── app.component.{ts,html,scss}
|
|
77
|
+
│ ├── environments/
|
|
78
|
+
│ │ ├── environment.ts
|
|
79
|
+
│ │ └── environment.prod.ts
|
|
80
|
+
│ ├── styles/ ← global tokens, themes
|
|
81
|
+
│ │ └── _tokens.scss
|
|
82
|
+
│ ├── main.ts ← bootstrapApplication(AppComponent, appConfig)
|
|
83
|
+
│ └── index.html
|
|
84
|
+
├── angular.json ← builder: "@angular/build:application" (esbuild)
|
|
85
|
+
├── tsconfig.json ← strict + noUncheckedIndexedAccess
|
|
86
|
+
├── jest.config.ts
|
|
87
|
+
├── playwright.config.ts
|
|
88
|
+
├── lighthouserc.json
|
|
89
|
+
└── package.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 4. Code patterns
|
|
93
|
+
|
|
94
|
+
### 4.1 Component file layout (THREE FILES — HARD RULE, NON-NEGOTIABLE)
|
|
95
|
+
|
|
96
|
+
Every component / directive / pipe with a template MUST have three physical files:
|
|
97
|
+
|
|
98
|
+
- `name.component.ts` — logic only, uses `templateUrl` and `styleUrl` / `styleUrls`.
|
|
99
|
+
- `name.component.html` — template.
|
|
100
|
+
- `name.component.scss` — styles.
|
|
101
|
+
|
|
102
|
+
NEVER inline `template:` or `styles:` / `styleUrls: ['...inline...']` inside the `.ts`. No size threshold, no "small component" exception, no presentation-only exception. `code-reviewer` / `tech-lead` reject merges that violate this.
|
|
103
|
+
|
|
104
|
+
### 4.2 Component with `resource()` (data fetching — greenfield default)
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// product-list.component.ts
|
|
108
|
+
import { Component, ChangeDetectionStrategy, inject, resource } from '@angular/core';
|
|
109
|
+
import { CommonModule } from '@angular/common';
|
|
110
|
+
import { firstValueFrom } from 'rxjs';
|
|
111
|
+
import { ProductService } from './product.service';
|
|
112
|
+
|
|
113
|
+
@Component({
|
|
114
|
+
selector: 'app-product-list',
|
|
115
|
+
standalone: true,
|
|
116
|
+
imports: [CommonModule],
|
|
117
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
118
|
+
templateUrl: './product-list.component.html',
|
|
119
|
+
styleUrl: './product-list.component.scss',
|
|
120
|
+
})
|
|
121
|
+
export class ProductListComponent {
|
|
122
|
+
private svc = inject(ProductService);
|
|
123
|
+
|
|
124
|
+
productsResource = resource({
|
|
125
|
+
loader: () => firstValueFrom(this.svc.list()),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<!-- product-list.component.html -->
|
|
132
|
+
@if (productsResource.isLoading()) {
|
|
133
|
+
<p>Loading...</p>
|
|
134
|
+
} @else if (productsResource.error(); as err) {
|
|
135
|
+
<p class="error">Failed to load: {{ err.message }}</p>
|
|
136
|
+
} @else {
|
|
137
|
+
<ul>
|
|
138
|
+
@for (p of productsResource.value(); track p.id) {
|
|
139
|
+
<li>{{ p.name }} — {{ p.price | currency }}</li>
|
|
140
|
+
}
|
|
141
|
+
</ul>
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Rule**: never manage `isLoading` / `error` signals by hand for HTTP calls — `resource()` owns that lifecycle.
|
|
146
|
+
|
|
147
|
+
### 4.3 Signal Forms (greenfield default — preferred over Reactive Forms)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// product-form.component.ts
|
|
151
|
+
import { Component, ChangeDetectionStrategy, output } from '@angular/core';
|
|
152
|
+
import { signalForm, control } from '@angular/forms';
|
|
153
|
+
import { required, maxLength, min } from '@angular/forms/validators';
|
|
154
|
+
|
|
155
|
+
export interface Product {
|
|
156
|
+
name: string;
|
|
157
|
+
price: number;
|
|
158
|
+
stock: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Component({
|
|
162
|
+
selector: 'app-product-form',
|
|
163
|
+
standalone: true,
|
|
164
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
165
|
+
templateUrl: './product-form.component.html',
|
|
166
|
+
styleUrl: './product-form.component.scss',
|
|
167
|
+
})
|
|
168
|
+
export class ProductFormComponent {
|
|
169
|
+
saved = output<Product>();
|
|
170
|
+
|
|
171
|
+
form = signalForm({
|
|
172
|
+
name: control('', { validators: [required(), maxLength(120)] }),
|
|
173
|
+
price: control(0, { validators: [required(), min(0)] }),
|
|
174
|
+
stock: control(0, { validators: [required(), min(0)] }),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
submit(): void {
|
|
178
|
+
if (this.form.valid()) {
|
|
179
|
+
this.saved.emit(this.form.value() as Product);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Rule**: `output()` function instead of `@Output()` decorator. Validators are functions returning `ValidatorFn` — typed end-to-end.
|
|
186
|
+
|
|
187
|
+
### 4.4 `@defer` for lazy sections
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
@defer (on viewport) {
|
|
191
|
+
<app-product-list />
|
|
192
|
+
} @loading (minimum 200ms) {
|
|
193
|
+
<div class="skeleton">Loading list...</div>
|
|
194
|
+
} @placeholder {
|
|
195
|
+
<div>Scroll to see products</div>
|
|
196
|
+
} @error {
|
|
197
|
+
<div class="error">Failed to load the section.</div>
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Rule**: use `@defer` for heavy, viewport-conditional sections. Route-level lazy is `loadComponent` (see 4.6). Combine both for max bundle savings.
|
|
202
|
+
|
|
203
|
+
### 4.5 Server Component / SSR pattern
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// product-detail.component.ts
|
|
207
|
+
import { Component, ChangeDetectionStrategy, input, resource } from '@angular/core';
|
|
208
|
+
import { firstValueFrom } from 'rxjs';
|
|
209
|
+
import { ProductService } from './product.service';
|
|
210
|
+
import { inject } from '@angular/core';
|
|
211
|
+
|
|
212
|
+
@Component({
|
|
213
|
+
selector: 'app-product-detail',
|
|
214
|
+
standalone: true,
|
|
215
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
216
|
+
templateUrl: './product-detail.component.html',
|
|
217
|
+
styleUrl: './product-detail.component.scss',
|
|
218
|
+
})
|
|
219
|
+
export class ProductDetailComponent {
|
|
220
|
+
id = input.required<string>();
|
|
221
|
+
private svc = inject(ProductService);
|
|
222
|
+
|
|
223
|
+
productResource = resource({
|
|
224
|
+
request: () => ({ id: this.id() }),
|
|
225
|
+
loader: ({ request }) => firstValueFrom(this.svc.byId(request.id)),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Rule**: signal-based renders are SSR-friendly out of the box. For protected pages, validate the JWT on the server-side render boundary (interceptor running in server env) — never trust client-only auth in hybrid rendering.
|
|
231
|
+
|
|
232
|
+
### 4.6 Lazy routes (`loadComponent`)
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// app.routes.ts
|
|
236
|
+
import { Routes } from '@angular/router';
|
|
237
|
+
|
|
238
|
+
export const routes: Routes = [
|
|
239
|
+
{
|
|
240
|
+
path: '',
|
|
241
|
+
loadComponent: () =>
|
|
242
|
+
import('./features/home/home.component').then(m => m.HomeComponent),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
path: 'products',
|
|
246
|
+
loadComponent: () =>
|
|
247
|
+
import('./features/products/product-list.component').then(m => m.ProductListComponent),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
path: 'products/:id',
|
|
251
|
+
loadComponent: () =>
|
|
252
|
+
import('./features/products/product-detail.component').then(m => m.ProductDetailComponent),
|
|
253
|
+
},
|
|
254
|
+
];
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 4.7 HttpClient + functional interceptor
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// core/interceptors/auth.interceptor.ts
|
|
261
|
+
import { HttpInterceptorFn } from '@angular/common/http';
|
|
262
|
+
import { inject } from '@angular/core';
|
|
263
|
+
import { AuthService } from '../auth/auth.service';
|
|
264
|
+
|
|
265
|
+
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
|
266
|
+
const token = inject(AuthService).token();
|
|
267
|
+
if (!token) {
|
|
268
|
+
return next(req);
|
|
269
|
+
}
|
|
270
|
+
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// app.config.ts
|
|
276
|
+
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
|
|
277
|
+
import { provideRouter } from '@angular/router';
|
|
278
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
279
|
+
import { routes } from './app.routes';
|
|
280
|
+
import { authInterceptor } from './core/interceptors/auth.interceptor';
|
|
281
|
+
|
|
282
|
+
export const appConfig: ApplicationConfig = {
|
|
283
|
+
providers: [
|
|
284
|
+
provideZonelessChangeDetection(),
|
|
285
|
+
provideRouter(routes),
|
|
286
|
+
provideHttpClient(withInterceptors([authInterceptor])),
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Rule**: zoneless is the default. `provideZoneChangeDetection()` only allowed during legacy migration with explicit ADR.
|
|
292
|
+
|
|
293
|
+
### 4.8 Signal-based state — immutability
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { signal } from '@angular/core';
|
|
297
|
+
|
|
298
|
+
const items = signal<string[]>([]);
|
|
299
|
+
|
|
300
|
+
// CORRECT — replace reference
|
|
301
|
+
items.update(arr => [...arr, 'new']);
|
|
302
|
+
|
|
303
|
+
// WRONG — mutates underlying array, breaks change detection guarantees
|
|
304
|
+
items().push('new');
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## 5. Testing
|
|
308
|
+
|
|
309
|
+
### Unit (Jest 30 + Testing Library 18)
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// product-list.component.spec.ts
|
|
313
|
+
import { render, screen } from '@testing-library/angular';
|
|
314
|
+
import { of } from 'rxjs';
|
|
315
|
+
import { ProductListComponent } from './product-list.component';
|
|
316
|
+
import { ProductService } from './product.service';
|
|
317
|
+
|
|
318
|
+
describe('ProductListComponent', () => {
|
|
319
|
+
it('renders products from resource', async () => {
|
|
320
|
+
const mockSvc = { list: () => of([{ id: '1', name: 'widget', price: 10 }]) };
|
|
321
|
+
await render(ProductListComponent, {
|
|
322
|
+
providers: [{ provide: ProductService, useValue: mockSvc }],
|
|
323
|
+
});
|
|
324
|
+
expect(await screen.findByText(/widget/)).toBeInTheDocument();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Signal Forms unit test
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { ProductFormComponent } from './product-form.component';
|
|
333
|
+
|
|
334
|
+
it('rejects invalid input', () => {
|
|
335
|
+
const c = new ProductFormComponent();
|
|
336
|
+
c.form.controls.name.setValue('');
|
|
337
|
+
c.form.controls.price.setValue(-5);
|
|
338
|
+
expect(c.form.valid()).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Accessibility (jest-axe — component level)
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { render } from '@testing-library/angular';
|
|
346
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
347
|
+
import { ProductFormComponent } from './product-form.component';
|
|
348
|
+
|
|
349
|
+
expect.extend(toHaveNoViolations);
|
|
350
|
+
|
|
351
|
+
it('has no a11y violations', async () => {
|
|
352
|
+
const { container } = await render(ProductFormComponent);
|
|
353
|
+
const results = await axe(container);
|
|
354
|
+
expect(results).toHaveNoViolations();
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### E2E (Playwright + axe)
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// e2e/products.spec.ts
|
|
362
|
+
import { test, expect } from '@playwright/test';
|
|
363
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
364
|
+
|
|
365
|
+
test('product list has no critical a11y violations', async ({ page }) => {
|
|
366
|
+
await page.goto('/products');
|
|
367
|
+
await expect(page.getByText(/widget/i)).toBeVisible();
|
|
368
|
+
const accessibilityScanResults = await new AxeBuilder({ page })
|
|
369
|
+
.withTags(['wcag2a', 'wcag2aa'])
|
|
370
|
+
.analyze();
|
|
371
|
+
expect(accessibilityScanResults.violations.filter(v => v.impact === 'critical' || v.impact === 'serious')).toEqual([]);
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Lighthouse CI (perf gate per ADR-007)
|
|
376
|
+
|
|
377
|
+
`lighthouserc.json` thresholds: performance >= 0.80, LCP <= 2500ms, CLS <= 0.1, TBT <= 300ms — `gate-keeper` fails the build below these.
|
|
378
|
+
|
|
379
|
+
## 6. Build & run commands
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
npm install
|
|
383
|
+
npm start # esbuild dev server on http://localhost:4200
|
|
384
|
+
npm run build -- --configuration=production # production bundle (esbuild application builder)
|
|
385
|
+
npm test -- --watchAll=false --coverage # Jest with coverage
|
|
386
|
+
npx playwright test # E2E suite
|
|
387
|
+
npx playwright test --ui # interactive runner
|
|
388
|
+
npx lhci autorun # Lighthouse CI against built app
|
|
389
|
+
npx eslint . --max-warnings 0 # zero-warning policy
|
|
390
|
+
npm audit --audit-level=high # CVE scan (0 HIGH, 0 CRITICAL per ADR-007)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## 7. Security (per ADR-007 + ADR-027 — MANDATORY section)
|
|
394
|
+
|
|
395
|
+
### 7.1 Authentication & Authorization
|
|
396
|
+
|
|
397
|
+
- **JWT** stored in memory (signal) or httpOnly cookie. NEVER `localStorage` for tokens — XSS risk.
|
|
398
|
+
- **Functional HttpInterceptor** attaches `Authorization: Bearer ...` (see 4.7).
|
|
399
|
+
- **`CanActivate` / `CanMatch` guards** for route-level auth + role checks. Use functional guards (`canActivateFn`).
|
|
400
|
+
- **Server Components**: validate JWT on the server-render boundary; do NOT rely on client-only checks for SSR'd pages.
|
|
401
|
+
- **Refresh tokens** rotated on every use; revoke on logout server-side.
|
|
402
|
+
|
|
403
|
+
### 7.2 CORS
|
|
404
|
+
|
|
405
|
+
Frontend does not configure CORS — backend owns it. Required: backend declares explicit `Access-Control-Allow-Origin` per environment, never `*` in production. Frontend asserts this in E2E by failing fast if cross-origin preflight fails.
|
|
406
|
+
|
|
407
|
+
### 7.3 Validation & XSS
|
|
408
|
+
|
|
409
|
+
- Angular template engine **auto-escapes** all interpolation by default — keep it that way.
|
|
410
|
+
- **NEVER** use `[innerHTML]` with untrusted input. If unavoidable, sanitize via `DomSanitizer.bypassSecurityTrustHtml(...)` ONLY after `DOMPurify` cleaning.
|
|
411
|
+
- **Signal Forms validators** are typed — invalid types fail at compile time, shrinking the exploit surface.
|
|
412
|
+
- **URL params** treated as untrusted: validate shape (UUID regex, enum match) before dispatching.
|
|
413
|
+
- **File upload**: enforce MIME + extension allowlist client-side AND server-side; never trust client MIME alone.
|
|
414
|
+
|
|
415
|
+
### 7.4 Secrets management
|
|
416
|
+
|
|
417
|
+
- NEVER commit secrets in `environment.ts` / `environment.prod.ts`.
|
|
418
|
+
- Build-time env vars injected via CI (`process.env.X` replaced by build tooling); production secrets resolved via Vault / AWS Secrets Manager / Azure Key Vault and injected into the runtime container, not the bundle.
|
|
419
|
+
- Local dev: `.env.local` in `.gitignore` OR `~/.secrets/<project>.env`.
|
|
420
|
+
- API keys for third-party JS SDKs scoped (e.g. Stripe publishable key vs secret key) and origin-restricted in the vendor dashboard.
|
|
421
|
+
|
|
422
|
+
### 7.5 Rate limiting
|
|
423
|
+
|
|
424
|
+
Backend responsibility (Bucket4j on Spring Boot or equivalent). Frontend MUST:
|
|
425
|
+
- Disable submit buttons during in-flight requests (`resource().isLoading()` driven).
|
|
426
|
+
- Surface `429 Too Many Requests` with a respectful retry UI (countdown + retry-after).
|
|
427
|
+
- Never bypass via parallel requests — orchestrate sequentially when the endpoint declares it.
|
|
428
|
+
|
|
429
|
+
### 7.6 OWASP Top 10 mapping
|
|
430
|
+
|
|
431
|
+
| OWASP | Mitigation in this stack |
|
|
432
|
+
|---|---|
|
|
433
|
+
| A01 Broken Access Control | `CanActivate` / `CanMatch` guards + role check; server enforces same — frontend is convenience, never the source of truth |
|
|
434
|
+
| A02 Cryptographic Failures | HTTPS only (HSTS via backend); JWT signed RS256; Subresource Integrity (SRI) hashes on any CDN-loaded script |
|
|
435
|
+
| A03 Injection / XSS | Template auto-escape; ban `[innerHTML]` with untrusted input; sanitize via DOMPurify + DomSanitizer when unavoidable |
|
|
436
|
+
| A04 Insecure Design | Threat model documented per feature; Server Component boundaries explicit in ADR |
|
|
437
|
+
| A05 Security Misconfiguration | CSP strict (no `unsafe-inline`, no `unsafe-eval`); `X-Frame-Options: DENY`; debug builds blocked from prod via env check |
|
|
438
|
+
| A06 Vulnerable Components | `npm audit --audit-level=high` in CI; Renovate / Dependabot weekly bumps; npm provenance enforced for critical deps |
|
|
439
|
+
| A07 Auth Failures | Backend rate limit + account lockout; frontend disables submit during request; MFA UI for critical flows |
|
|
440
|
+
| A08 Data Integrity | SRI hashes on CDN scripts; supply chain via npm provenance + SBOM (CycloneDX); lockfile committed and verified in CI |
|
|
441
|
+
| A09 Logging Failures | Browser logs scrubbed for PII; correlation ID propagated via header from interceptor; never log tokens or full request bodies |
|
|
442
|
+
| A10 SSRF | N/A for pure client; Server Components with `fetch()` MUST validate target URL against allowlist before request |
|
|
443
|
+
|
|
444
|
+
### 7.7 LGPD / GDPR specifics
|
|
445
|
+
|
|
446
|
+
- **Opt-in cookies** — banner gates non-essential cookies; user choice persisted and respected by analytics modules (lazy-load analytics only after consent).
|
|
447
|
+
- **PII tagging** — mark fields containing PII (email, CPF, phone) with a const map; logging interceptor strips tagged fields before sending to observability sink.
|
|
448
|
+
- **Data subject access** — UI route `/account/data-export` triggers backend export; `account/delete` triggers soft-delete + audit log.
|
|
449
|
+
- **Consent versioning** — track which consent version the user accepted; re-prompt on material policy changes.
|
|
450
|
+
|
|
451
|
+
## 8. Anti-patterns (block in code-review)
|
|
452
|
+
|
|
453
|
+
| Bad | Good | Why |
|
|
454
|
+
|---|---|---|
|
|
455
|
+
| Inline `template:` / `styles:` in `.ts` | Three separate files (`.ts` + `.html` + `.scss`) | HARD rule — non-negotiable, overrides any prior threshold-based policy |
|
|
456
|
+
| `NgModule` | `standalone: true` components | NgModule deprecated in 21; new code must be standalone |
|
|
457
|
+
| `any` in TypeScript | Explicit type or `unknown` + narrow | Strict mode mandatory — `any` defeats the type system |
|
|
458
|
+
| `@Output() event = new EventEmitter()` | `event = output<T>()` | Decorator is deprecated; function form is typed and SSR-friendly |
|
|
459
|
+
| Reactive Forms in new code | Signal Forms (`signalForm({...})`) | Signal Forms are stable in 21 and aligned with the rest of the stack |
|
|
460
|
+
| Manual `isLoading` signal for HTTP | `resource({ loader })` | `resource()` owns lifecycle, error, refetch, abort |
|
|
461
|
+
| `provideZoneChangeDetection()` in new project | `provideZonelessChangeDetection()` | Zoneless is the greenfield default |
|
|
462
|
+
| `items().push(x)` (mutation) | `items.update(arr => [...arr, x])` | Mutation breaks signal change tracking |
|
|
463
|
+
| `[innerHTML]="userInput"` | DomSanitizer + DOMPurify | XSS vector |
|
|
464
|
+
| `ChangeDetectionStrategy.Default` | `ChangeDetectionStrategy.OnPush` | OnPush is mandatory — Default re-renders entire tree |
|
|
465
|
+
| Routes-only lazy loading for heavy panels | `@defer (on viewport)` for sections, `loadComponent` for routes | Combine both for max bundle savings |
|
|
466
|
+
| `browser` (webpack) builder | `application` (esbuild) builder | Webpack builder removed; esbuild is mandatory |
|
|
467
|
+
| `localStorage.setItem('token', ...)` | In-memory signal OR httpOnly cookie | localStorage is XSS-accessible |
|
|
468
|
+
| `@Autowired`-style field assignment via constructor without `inject()` for new code | `inject(Service)` | Functional `inject()` is idiomatic and works in interceptors/guards |
|
|
469
|
+
| Eager loading every feature in `app.routes.ts` | `loadComponent: () => import(...)` | Bundle bloat — eager routes break LCP |
|
|
470
|
+
|
|
471
|
+
## 9. Migration hints — Angular 21 → 22 (future)
|
|
472
|
+
|
|
473
|
+
When `angular-22.md` lands, expect (subject to actual release notes):
|
|
474
|
+
|
|
475
|
+
- TypeScript 5.7+ baseline.
|
|
476
|
+
- Further evolutions of Server Components (likely streaming improvements and more `use server` directives).
|
|
477
|
+
- Additional signal-based primitives (e.g. richer `linkedSignal`, async signals).
|
|
478
|
+
- Possible deprecation of remaining Zone.js compat shims.
|
|
479
|
+
- Stricter ESLint rules around `@Output` (likely removal) and NgModule (likely removal).
|
|
480
|
+
- Use `migrator` agent — it knows both packs and proposes an ADR per cross.
|
|
481
|
+
|
|
482
|
+
## 10. References
|
|
483
|
+
|
|
484
|
+
- ADR-007 (Senior+ gate thresholds — a11y, Lighthouse, pyramid)
|
|
485
|
+
- ADR-026 (Generic agents + stack packs architecture)
|
|
486
|
+
- ADR-027 (Pack governance — frontmatter + security + CODEOWNERS)
|
|
487
|
+
- ADR-029 (Canonical pack format — 10 sections)
|
|
488
|
+
- Angular 21 release notes: https://blog.angular.dev/
|
|
489
|
+
- Angular Update Guide: https://angular.dev/update-guide
|
|
490
|
+
- Signal Forms spec: https://angular.dev/guide/signal-forms
|
|
491
|
+
- Resource API spec: https://angular.dev/guide/signals/resource
|
|
492
|
+
- Server Components spec: https://angular.dev/guide/ssr
|
|
493
|
+
- Lighthouse CI: https://github.com/GoogleChrome/lighthouse-ci
|
|
494
|
+
- OWASP Top 10 (2021): https://owasp.org/Top10/
|