@synergyerp/frontend-standards 1.1.2 → 1.1.4

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.
@@ -45,9 +45,9 @@
45
45
 
46
46
  This document is split into two distinct parts:
47
47
 
48
- | Part | Focus | Audience |
49
- |------|-------|----------|
50
- | **Frontend Standards** (Sections 2-9) | Code structure, linting, UI components, testing, git | Developers, Tech Leads |
48
+ | Part | Focus | Audience |
49
+ | --------------------------------------- | -------------------------------------------------------- | ---------------------- |
50
+ | **Frontend Standards** (Sections 2-9) | Code structure, linting, UI components, testing, git | Developers, Tech Leads |
51
51
  | **CI/CD & Deployment** (Sections 10-14) | Pipelines, code review, deployment, monitoring, security | DevOps, QA, Tech Leads |
52
52
 
53
53
  ---
@@ -56,41 +56,41 @@ This document is split into two distinct parts:
56
56
 
57
57
  ### 2.1 Framework & Runtime
58
58
 
59
- | Layer | Recommended | Alternative | Notes |
60
- |-------|------------|-------------|-------|
61
- | **Framework** | React 19 + Vite | Next.js(if SSR/SEO needed) | Choose based on project needs. Lovable generates React components natively. |
62
- | **Language** | TypeScript 5.5+ (strict mode) | -- | Non-negotiable. No plain JavaScript in new projects. |
63
- | **Build Tool** | Vite 6 | Turbopack (Next.js only) | Fastest build times. |
64
- | **Package Manager** | Any (pnpm 9+ preferred) | npm 10+, yarn, bun | Developers may use any manager locally; however, **pnpm** is preferred for CI/CD and deployment for performance and consistency. |
59
+ | Layer | Recommended | Alternative | Notes |
60
+ | ------------------- | ----------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
61
+ | **Framework** | React 19 + Vite | Next.js(if SSR/SEO needed) | Choose based on project needs. Lovable generates React components natively. |
62
+ | **Language** | TypeScript 5.5+ (strict mode) | -- | Non-negotiable. No plain JavaScript in new projects. |
63
+ | **Build Tool** | Vite 6 | Turbopack (Next.js only) | Fastest build times. |
64
+ | **Package Manager** | Any (pnpm 9+ preferred) | npm 10+, yarn, bun | Developers may use any manager locally; however, **pnpm** is preferred for CI/CD and deployment for performance and consistency. |
65
65
 
66
66
  ### 2.2 UI & Styling
67
67
 
68
- | Layer | Recommendation | Notes |
69
- |-------|---------------|-------|
70
- | **UI Components** | Lovable.ai generated + Radix UI primitives | Lovable generates the initial templates and components. Hand-roll custom logic on top. |
71
- | **Styling** | Tailwind CSS v4 | Utility-first. Compatible with Lovable output. |
72
- | **Component Library** | Shadcn UI (or custom) | Copy-pasteable components, fully customizable. |
73
- | **Icons** | Lucide React | Consistent, tree-shakeable. |
74
- | **Animations** | Framer Motion / tw-animate-css | For complex animations vs simple transitions. |
68
+ | Layer | Recommendation | Notes |
69
+ | --------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------- |
70
+ | **UI Components** | Lovable.ai generated + Radix UI primitives | Lovable generates the initial templates and components. Hand-roll custom logic on top. |
71
+ | **Styling** | Tailwind CSS v4 | Utility-first. Compatible with Lovable output. |
72
+ | **Component Library** | Shadcn UI (or custom) | Copy-pasteable components, fully customizable. |
73
+ | **Icons** | Lucide React | Consistent, tree-shakeable. |
74
+ | **Animations** | Framer Motion / tw-animate-css | For complex animations vs simple transitions. |
75
75
 
76
76
  ### 2.3 State & Data
77
77
 
78
- | Layer | Recommendation | Notes |
79
- |-------|---------------|-------|
80
- | **Server State** | TanStack React Query (v5) | Caching, refetching, optimistic updates. |
81
- | **Client State** | Zustand | Lightweight, no boilerplate. |
82
- | **Forms** | React Hook Form + Zod | Validation schema-driven. |
83
- | **Routing** | React Router v7 / Next.js App Router | Depends on framework choice. |
78
+ | Layer | Recommendation | Notes |
79
+ | ---------------- | ------------------------------------ | ---------------------------------------- |
80
+ | **Server State** | TanStack React Query (v5) | Caching, refetching, optimistic updates. |
81
+ | **Client State** | Zustand | Lightweight, no boilerplate. |
82
+ | **Forms** | React Hook Form + Zod | Validation schema-driven. |
83
+ | **Routing** | React Router v7 / Next.js App Router | Depends on framework choice. |
84
84
 
85
85
  ### 2.4 Testing
86
86
 
87
- | Layer | Tool | Notes |
88
- |-------|------|-------|
89
- | **Unit / Component** | Vitest (preferred) or Jest 29 | Vitest is faster, native ESM, Vite-compatible. |
90
- | **Component Testing** | Testing Library + jsdom | Test behavior, not implementation. |
91
- | **E2E** | Playwright | Cross-browser, reliable, parallel. |
92
- | **Visual Regression** | Playwright / Chromatic | For UI component libraries. |
93
- | **Coverage** | c8 / istanbul | Minimum 80% line/branch. |
87
+ | Layer | Tool | Notes |
88
+ | --------------------- | ----------------------------- | ---------------------------------------------- |
89
+ | **Unit / Component** | Vitest (preferred) or Jest 29 | Vitest is faster, native ESM, Vite-compatible. |
90
+ | **Component Testing** | Testing Library + jsdom | Test behavior, not implementation. |
91
+ | **E2E** | Playwright | Cross-browser, reliable, parallel. |
92
+ | **Visual Regression** | Playwright / Chromatic | For UI component libraries. |
93
+ | **Coverage** | c8 / istanbul | Minimum 80% line/branch. |
94
94
 
95
95
  ---
96
96
 
@@ -165,14 +165,14 @@ All domain-specific files across **every** directory (`services/`, `hooks/`, `st
165
165
 
166
166
  Every domain subdirectory **must** expose its contents via an `index.ts` barrel file. Parent code imports from the barrel, never from individual files within a domain.
167
167
 
168
- | Directory | Modularization Rule | Barrel Required |
169
- |-----------|---------------------|:---:|
170
- | `services/` | One subdirectory per domain (e.g., `services/employee/`, `services/auth/`). Only `api-client.ts` may live at the root. | Yes |
171
- | `hooks/` | Domain-specific hooks in `hooks/<domain>/`. Shared utility hooks (`useDebounce`) may live at root. | Yes |
172
- | `store/` | Domain-specific Zustand stores in `store/<domain>/`. Cross-cutting stores only at root. | Yes |
173
- | `components/` | Domain-specific components in `components/<domain>/`. Shared UI primitives in `components/ui/`. | Yes |
174
- | `lib/` | Domain-specific utilities in `lib/<domain>/`. Cross-domain utilities may live at root. | Yes |
175
- | `types/` | Domain-specific types in `types/<domain>/`. Shared types at root (`types/user.ts`). | Yes |
168
+ | Directory | Modularization Rule | Barrel Required |
169
+ | ------------- | ---------------------------------------------------------------------------------------------------------------------- | :-------------: |
170
+ | `services/` | One subdirectory per domain (e.g., `services/employee/`, `services/auth/`). Only `api-client.ts` may live at the root. | Yes |
171
+ | `hooks/` | Domain-specific hooks in `hooks/<domain>/`. Shared utility hooks (`useDebounce`) may live at root. | Yes |
172
+ | `store/` | Domain-specific Zustand stores in `store/<domain>/`. Cross-cutting stores only at root. | Yes |
173
+ | `components/` | Domain-specific components in `components/<domain>/`. Shared UI primitives in `components/ui/`. | Yes |
174
+ | `lib/` | Domain-specific utilities in `lib/<domain>/`. Cross-domain utilities may live at root. | Yes |
175
+ | `types/` | Domain-specific types in `types/<domain>/`. Shared types at root (`types/user.ts`). | Yes |
176
176
 
177
177
  **Barrel File (`index.ts`) Convention**:
178
178
 
@@ -243,7 +243,7 @@ These options ensure a modern, strict, and performant TypeScript environment:
243
243
  - **noEmit: true**: Relies on the bundler (Vite) for emission, using tsc only for type checking.
244
244
  - **jsx: react-jsx**: Enables the modern React JSX transform.
245
245
  - **baseUrl: .**: Sets the root for non-relative module resolution.
246
- - **paths**: Configures path aliases (e.g., @/*) for cleaner imports.
246
+ - **paths**: Configures path aliases (e.g., @/\*) for cleaner imports.
247
247
 
248
248
  ```jsonc
249
249
  {
@@ -261,8 +261,8 @@ These options ensure a modern, strict, and performant TypeScript environment:
261
261
  "noEmit": true,
262
262
  "jsx": "react-jsx",
263
263
  "baseUrl": ".",
264
- "paths": { "@/*": ["src/*"] }
265
- }
264
+ "paths": { "@/*": ["src/*"] },
265
+ },
266
266
  }
267
267
  ```
268
268
 
@@ -279,17 +279,17 @@ These options ensure a modern, strict, and performant TypeScript environment:
279
279
 
280
280
  ### 5.1 ESLint (v9 Flat Config) - Required Plugins
281
281
 
282
- | Plugin | Purpose |
283
- |--------|---------|
284
- | @eslint/js | ESLint recommended rules |
285
- | typescript-eslint | TypeScript-specific rules |
286
- | eslint-plugin-react | React-specific rules |
287
- | eslint-plugin-react-hooks | React Hooks rules |
288
- | eslint-plugin-import | Import ordering |
289
- | eslint-plugin-unicorn | Modern JS best practices |
290
- | eslint-plugin-prettier | Prettier as ESLint rule |
291
- | eslint-plugin-jsx-a11y | Accessibility checks |
292
- | eslint-plugin-boundaries | Enforce domain-based modularization and folder dependency rules |
282
+ | Plugin | Purpose |
283
+ | ------------------------- | --------------------------------------------------------------- |
284
+ | @eslint/js | ESLint recommended rules |
285
+ | typescript-eslint | TypeScript-specific rules |
286
+ | eslint-plugin-react | React-specific rules |
287
+ | eslint-plugin-react-hooks | React Hooks rules |
288
+ | eslint-plugin-import | Import ordering |
289
+ | eslint-plugin-unicorn | Modern JS best practices |
290
+ | eslint-plugin-prettier | Prettier as ESLint rule |
291
+ | eslint-plugin-jsx-a11y | Accessibility checks |
292
+ | eslint-plugin-boundaries | Enforce domain-based modularization and folder dependency rules |
293
293
 
294
294
  ### 5.2 Naming Conventions -- Complete Reference
295
295
 
@@ -298,44 +298,46 @@ These options ensure a modern, strict, and performant TypeScript environment:
298
298
  A variable name should tell you what it contains, not how it's implemented. If you need a comment to explain what a variable holds, the name is wrong.
299
299
 
300
300
  **Single-letter variables are strictly prohibited** except:
301
+
301
302
  - Loop index i, j, k inside a for loop
302
- - Array callback _ for unused parameters
303
+ - Array callback \_ for unused parameters
303
304
  - Math coordinates x, y, z (must be documented)
304
305
 
305
306
  #### Naming Reference Table
306
307
 
307
- | Entity | Convention | Good Example | Bad Example |
308
- |--------|-----------|-------------|-------------|
309
- | Files (General) | kebab-case | user-service.ts | UserService.ts |
310
- | Files (React Components) | PascalCase | UserProfile.tsx | user-profile.tsx |
311
- | Directories | kebab-case | user-profile/ | UserProfile/ |
312
- | Variables (general) | camelCase | userCount, isLoading | uc, l, data |
313
- | Booleans | camelCase + prefix | isLoading, hasError, canEdit | loading, error |
314
- | Functions | camelCase (verb+noun) | getUserById(), handleSubmit() | get(), doStuff() |
315
- | Constants (module) | UPPER_SNAKE_CASE | MAX_RETRY_COUNT | maxRetryCount |
316
- | Types/Interfaces | PascalCase | UserProfileProps | userProfile |
317
- | Enums | PascalCase + UPPER values | Role.ADMIN | role.admin |
318
- | Event handlers | handle + Event | handleSubmit | submitHandler |
319
- | Handler props | on + Event | onSubmit, onClick | submit, clicked |
320
- | Env variables | UPPER_SNAKE + prefix | VITE_API_URL | apiUrl |
321
- | Mock data | mock + Entity | mockUser, mockResponse | testData |
308
+ | Entity | Convention | Good Example | Bad Example |
309
+ | ------------------------ | ------------------------- | ----------------------------- | ---------------- |
310
+ | Files (General) | kebab-case | user-service.ts | UserService.ts |
311
+ | Files (React Components) | PascalCase | UserProfile.tsx | user-profile.tsx |
312
+ | Directories | kebab-case | user-profile/ | UserProfile/ |
313
+ | Variables (general) | camelCase | userCount, isLoading | uc, l, data |
314
+ | Booleans | camelCase + prefix | isLoading, hasError, canEdit | loading, error |
315
+ | Functions | camelCase (verb+noun) | getUserById(), handleSubmit() | get(), doStuff() |
316
+ | Constants (module) | UPPER_SNAKE_CASE | MAX_RETRY_COUNT | maxRetryCount |
317
+ | Types/Interfaces | PascalCase | UserProfileProps | userProfile |
318
+ | Enums | PascalCase + UPPER values | Role.ADMIN | role.admin |
319
+ | Event handlers | handle + Event | handleSubmit | submitHandler |
320
+ | Handler props | on + Event | onSubmit, onClick | submit, clicked |
321
+ | Env variables | UPPER_SNAKE + prefix | VITE_API_URL | apiUrl |
322
+ | Mock data | mock + Entity | mockUser, mockResponse | testData |
322
323
 
323
324
  #### Banned Variable Names
324
325
 
325
- | Banned | Why | Use Instead |
326
- |--------|-----|-------------|
327
- | const data | Means anything | const users, const response |
328
- | const temp | Temporary to whom? | const formattedDate |
329
- | const arr | Hungarian + meaningless | const permissions |
330
- | const obj | Everything is an object | const config |
331
- | const str | Hungarian notation | const message |
332
- | const result | All functions return a result | const validationResult |
333
- | const myVar | my prefix adds nothing | const username |
334
- | function handle() | Handle what? | function handleFormSubmit() |
326
+ | Banned | Why | Use Instead |
327
+ | ----------------- | ----------------------------- | --------------------------- |
328
+ | const data | Means anything | const users, const response |
329
+ | const temp | Temporary to whom? | const formattedDate |
330
+ | const arr | Hungarian + meaningless | const permissions |
331
+ | const obj | Everything is an object | const config |
332
+ | const str | Hungarian notation | const message |
333
+ | const result | All functions return a result | const validationResult |
334
+ | const myVar | my prefix adds nothing | const username |
335
+ | function handle() | Handle what? | function handleFormSubmit() |
335
336
 
336
337
  #### Boolean Naming
337
338
 
338
339
  Booleans must use a prefix that makes true/false obvious:
340
+
339
341
  ```
340
342
  GOOD: isLoading, hasPermission, canSubmit, shouldRender, isVisible, wasProcessed
341
343
  BAD: loading, permission, submit, render, visible, processed
@@ -376,67 +378,67 @@ Configuration for enforcing domain-based folder modularization across **all** di
376
378
 
377
379
  ```javascript
378
380
  // eslint.config.js (flat config)
379
- import boundaries from "eslint-plugin-boundaries";
380
- import importPlugin from "eslint-plugin-import";
381
+ import boundaries from 'eslint-plugin-boundaries';
382
+ import importPlugin from 'eslint-plugin-import';
381
383
 
382
384
  export default [
383
385
  // ... other configs
384
386
  {
385
387
  plugins: { boundaries, import: importPlugin },
386
388
  settings: {
387
- "boundaries/include": ["src/**/*"],
388
- "boundaries/elements": [
389
+ 'boundaries/include': ['src/**/*'],
390
+ 'boundaries/elements': [
389
391
  // Shared/global files (can be imported by any domain)
390
392
  {
391
- mode: "full",
392
- type: "shared",
393
+ mode: 'full',
394
+ type: 'shared',
393
395
  pattern: [
394
- "src/services/api-client.ts",
395
- "src/hooks/use-debounce.ts",
396
- "src/hooks/use-media-query.ts",
397
- "src/components/ui/**",
398
- "src/lib/*.ts",
399
- "src/lib/*.tsx",
400
- "src/types/!(*/**).ts",
396
+ 'src/services/api-client.ts',
397
+ 'src/hooks/use-debounce.ts',
398
+ 'src/hooks/use-media-query.ts',
399
+ 'src/components/ui/**',
400
+ 'src/lib/*.ts',
401
+ 'src/lib/*.tsx',
402
+ 'src/types/!(*/**).ts',
401
403
  ],
402
404
  },
403
405
  // Employee domain -- all directories
404
406
  {
405
- mode: "full",
406
- type: "employee",
407
+ mode: 'full',
408
+ type: 'employee',
407
409
  pattern: [
408
- "src/services/employee/**",
409
- "src/hooks/employee/**",
410
- "src/store/employee/**",
411
- "src/components/employee/**",
412
- "src/lib/employee/**",
413
- "src/types/employee/**",
410
+ 'src/services/employee/**',
411
+ 'src/hooks/employee/**',
412
+ 'src/store/employee/**',
413
+ 'src/components/employee/**',
414
+ 'src/lib/employee/**',
415
+ 'src/types/employee/**',
414
416
  ],
415
417
  },
416
418
  // Auth domain -- all directories
417
419
  {
418
- mode: "full",
419
- type: "auth",
420
+ mode: 'full',
421
+ type: 'auth',
420
422
  pattern: [
421
- "src/services/auth/**",
422
- "src/hooks/auth/**",
423
- "src/store/auth/**",
424
- "src/components/auth/**",
425
- "src/lib/auth/**",
426
- "src/types/auth/**",
423
+ 'src/services/auth/**',
424
+ 'src/hooks/auth/**',
425
+ 'src/store/auth/**',
426
+ 'src/components/auth/**',
427
+ 'src/lib/auth/**',
428
+ 'src/types/auth/**',
427
429
  ],
428
430
  },
429
431
  // Users domain -- all directories
430
432
  {
431
- mode: "full",
432
- type: "users",
433
+ mode: 'full',
434
+ type: 'users',
433
435
  pattern: [
434
- "src/services/users/**",
435
- "src/hooks/users/**",
436
- "src/store/users/**",
437
- "src/components/users/**",
438
- "src/lib/users/**",
439
- "src/types/users/**",
436
+ 'src/services/users/**',
437
+ 'src/hooks/users/**',
438
+ 'src/store/users/**',
439
+ 'src/components/users/**',
440
+ 'src/lib/users/**',
441
+ 'src/types/users/**',
440
442
  ],
441
443
  },
442
444
  ],
@@ -445,44 +447,48 @@ export default [
445
447
  // === BOUNDARIES RULES ===
446
448
 
447
449
  // BLOCK: flat/orphaned domain files at directory roots (enforces subdirectory pattern)
448
- "boundaries/entry-point": [
449
- "error",
450
+ 'boundaries/entry-point': [
451
+ 'error',
450
452
  {
451
- default: "disallow",
453
+ default: 'disallow',
452
454
  rules: [
453
455
  // Allow ONLY api-client.ts at the services/ root level
454
456
  {
455
- target: ["src/services/**/*.ts"],
456
- allow: "src/services/api-client.ts",
457
+ target: ['src/services/**/*.ts'],
458
+ allow: 'src/services/api-client.ts',
457
459
  },
458
460
  // Allow shared utility hooks at root but NOT domain-specific ones
459
461
  {
460
- target: ["src/hooks/use-employee*.ts", "src/hooks/use-auth*.ts", "src/hooks/use-user*.ts"],
461
- disallow: "src/hooks/",
462
+ target: [
463
+ 'src/hooks/use-employee*.ts',
464
+ 'src/hooks/use-auth*.ts',
465
+ 'src/hooks/use-user*.ts',
466
+ ],
467
+ disallow: 'src/hooks/',
462
468
  },
463
469
  // No domain-specific files at store/ root
464
470
  {
465
- target: ["src/store/**/*.ts"],
466
- allow: "src/store/index.ts",
471
+ target: ['src/store/**/*.ts'],
472
+ allow: 'src/store/index.ts',
467
473
  },
468
474
  ],
469
475
  },
470
476
  ],
471
477
 
472
478
  // BLOCK: cross-domain imports (e.g., employee component cannot import from auth services)
473
- "boundaries/element-types": [
474
- "error",
479
+ 'boundaries/element-types': [
480
+ 'error',
475
481
  {
476
- default: "disallow",
482
+ default: 'disallow',
477
483
  rules: [
478
484
  // Shared files can be imported by any domain
479
- { from: ["employee", "auth", "users"], allow: ["shared"] },
485
+ { from: ['employee', 'auth', 'users'], allow: ['shared'] },
480
486
  // Domains cannot import from other domains
481
- { from: ["employee"], disallow: ["auth", "users"] },
482
- { from: ["auth"], disallow: ["employee", "users"] },
483
- { from: ["users"], disallow: ["employee", "auth"] },
487
+ { from: ['employee'], disallow: ['auth', 'users'] },
488
+ { from: ['auth'], disallow: ['employee', 'users'] },
489
+ { from: ['users'], disallow: ['employee', 'auth'] },
484
490
  // Shared can import from shared
485
- { from: ["shared"], allow: ["shared"] },
491
+ { from: ['shared'], allow: ['shared'] },
486
492
  ],
487
493
  },
488
494
  ],
@@ -490,47 +496,49 @@ export default [
490
496
  // === IMPORT RULES: BLOCK deep imports bypassing barrel files ===
491
497
 
492
498
  // BLOCK: importing from a non-index file inside a domain subdirectory
493
- "import/no-internal-modules": [
494
- "error",
499
+ 'import/no-internal-modules': [
500
+ 'error',
495
501
  {
496
502
  allow: [
497
503
  // Only allow importing the barrel (index.ts) from each domain directory
498
- "src/services/*/index",
499
- "src/hooks/*/index",
500
- "src/store/*/index",
501
- "src/components/*/index",
502
- "src/lib/*/index",
503
- "src/types/*/index",
504
+ 'src/services/*/index',
505
+ 'src/hooks/*/index',
506
+ 'src/store/*/index',
507
+ 'src/components/*/index',
508
+ 'src/lib/*/index',
509
+ 'src/types/*/index',
504
510
  // Allow shared/utility files
505
- "src/services/api-client",
506
- "src/hooks/use-debounce",
507
- "src/hooks/use-media-query",
508
- "src/components/ui/**",
509
- "src/lib/*",
510
- "src/types/*",
511
+ 'src/services/api-client',
512
+ 'src/hooks/use-debounce',
513
+ 'src/hooks/use-media-query',
514
+ 'src/components/ui/**',
515
+ 'src/lib/*',
516
+ 'src/types/*',
511
517
  // Allow root-level globals
512
- "src/app/**",
513
- "src/styles/**",
514
- "src/test/**",
518
+ 'src/app/**',
519
+ 'src/styles/**',
520
+ 'src/test/**',
515
521
  ],
516
522
  },
517
523
  ],
518
524
 
519
525
  // BLOCK: importing from index files across domain boundaries (backup for boundaries plugin)
520
- "import/no-restricted-paths": [
521
- "error",
526
+ 'import/no-restricted-paths': [
527
+ 'error',
522
528
  {
523
529
  zones: [
524
530
  // Employee domain may not import from other domains
525
531
  {
526
- target: "./src/**/employee/**",
527
- from: "./src/**/auth/**",
528
- message: "Employee domain must not import from Auth domain. Use shared interfaces instead.",
532
+ target: './src/**/employee/**',
533
+ from: './src/**/auth/**',
534
+ message:
535
+ 'Employee domain must not import from Auth domain. Use shared interfaces instead.',
529
536
  },
530
537
  {
531
- target: "./src/**/employee/**",
532
- from: "./src/**/users/**",
533
- message: "Employee domain must not import from Users domain. Use shared interfaces instead.",
538
+ target: './src/**/employee/**',
539
+ from: './src/**/users/**',
540
+ message:
541
+ 'Employee domain must not import from Users domain. Use shared interfaces instead.',
534
542
  },
535
543
  // (repeat for each domain pair)
536
544
  ],
@@ -543,12 +551,12 @@ export default [
543
551
 
544
552
  **Enforcement Summary**:
545
553
 
546
- | Rule | What It Catches | Severity |
547
- |------|----------------|----------|
548
- | `boundaries/entry-point` | Flat domain files at directory roots (e.g., `services/employeeService.ts` instead of `services/employee/`) | `error` |
549
- | `boundaries/element-types` | Cross-domain imports (e.g., `employee` component importing from `auth` services) | `error` |
550
- | `import/no-internal-modules` | **Deep imports bypassing barrel files** (e.g., `from '@/services/employee/employee-bio-detail.service'` instead of `from '@/services/employee'`) | `error` |
551
- | `import/no-restricted-paths` | Backup enforcement of cross-domain import restrictions | `error` |
554
+ | Rule | What It Catches | Severity |
555
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
556
+ | `boundaries/entry-point` | Flat domain files at directory roots (e.g., `services/employeeService.ts` instead of `services/employee/`) | `error` |
557
+ | `boundaries/element-types` | Cross-domain imports (e.g., `employee` component importing from `auth` services) | `error` |
558
+ | `import/no-internal-modules` | **Deep imports bypassing barrel files** (e.g., `from '@/services/employee/employee-bio-detail.service'` instead of `from '@/services/employee'`) | `error` |
559
+ | `import/no-restricted-paths` | Backup enforcement of cross-domain import restrictions | `error` |
552
560
 
553
561
  **Barrel File Enforcement Flow**:
554
562
 
@@ -567,6 +575,7 @@ export default [
567
575
  ```
568
576
 
569
577
  **Adding a New Domain**:
578
+
570
579
  1. Create the domain subdirectories across all folders:
571
580
  ```
572
581
  services/<domain>/
@@ -601,17 +610,17 @@ Is it a transformer?--> format/convert prefix
601
610
  const d = new Date();
602
611
  const r = await fetch('/api/users');
603
612
  const arr = [1, 2, 3];
604
- const temp = users.filter(u => u.active);
605
- function get() { }
606
- function handle() { }
613
+ const temp = users.filter((u) => u.active);
614
+ function get() {}
615
+ function handle() {}
607
616
 
608
617
  // GOOD -- Self-documenting
609
618
  const currentDate = new Date();
610
619
  const response = await fetch('/api/users');
611
620
  const userIds = [1, 2, 3];
612
- const activeUsers = users.filter(user => user.isActive);
613
- function getActiveUsers() { }
614
- function handleFormSubmit() { }
621
+ const activeUsers = users.filter((user) => user.isActive);
622
+ function getActiveUsers() {}
623
+ function handleFormSubmit() {}
615
624
  ```
616
625
 
617
626
  ### 5.3 Import Order Standard
@@ -632,6 +641,7 @@ import { UserCard } from './user-card';
632
641
  ### 5.4 Lovable-Generated Code Handling
633
642
 
634
643
  When Lovable generates UI templates, always:
644
+
635
645
  1. Run generated code through ESLint + Prettier before committing.
636
646
  2. Extract reusable components into components/shared/ directory.
637
647
  3. Add proper TypeScript types to any untyped props.
@@ -663,6 +673,7 @@ export default {
663
673
  ### 6.2 Format-On-Save (VS Code)
664
674
 
665
675
  Every project must have .vscode/settings.json:
676
+
666
677
  ```json
667
678
  {
668
679
  "editor.formatOnSave": true,
@@ -741,7 +752,10 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
741
752
  }
742
753
 
743
754
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
744
- logger.error('ErrorBoundary caught an error', { error, componentStack: errorInfo.componentStack });
755
+ logger.error('ErrorBoundary caught an error', {
756
+ error,
757
+ componentStack: errorInfo.componentStack,
758
+ });
745
759
  this.props.onError?.(error, errorInfo);
746
760
  }
747
761
 
@@ -754,7 +768,10 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
754
768
  <div role="alert" className="flex flex-col items-center justify-center min-h-[400px] p-8">
755
769
  <h2 className="text-xl font-semibold mb-2">Something went wrong</h2>
756
770
  <p className="text-muted-foreground mb-4">An unexpected error occurred.</p>
757
- <button onClick={this.handleReset} className="px-4 py-2 bg-primary text-primary-foreground rounded-md">
771
+ <button
772
+ onClick={this.handleReset}
773
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
774
+ >
758
775
  Try Again
759
776
  </button>
760
777
  </div>
@@ -766,6 +783,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
766
783
  ```
767
784
 
768
785
  **Usage**:
786
+
769
787
  ```tsx
770
788
  // Route-level wrapping
771
789
  <ErrorBoundary onError={(err) => captureException(err)}>
@@ -774,6 +792,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
774
792
  ```
775
793
 
776
794
  **Rules**:
795
+
777
796
  - One ErrorBoundary per route (not per component) for granularity
778
797
  - Log errors to Sentry via `onError` callback
779
798
  - Never wrap individual small components (<50 lines) in their own ErrorBoundary
@@ -806,6 +825,7 @@ export function EmployeeRoutes() {
806
825
  ```
807
826
 
808
827
  **Rules**:
828
+
809
829
  - Every page/route component must use `React.lazy()` + dynamic `import()`
810
830
  - Every lazy route must have a `<Suspense>` wrapper with a skeleton fallback
811
831
  - Prefetch critical routes (dashboard, home) on link hover or visibility
@@ -816,12 +836,12 @@ export function EmployeeRoutes() {
816
836
 
817
837
  **Loading States**:
818
838
 
819
- | Context | Pattern | When |
820
- |---------|---------|------|
821
- | Page load | Skeleton screen | Initial page navigation |
822
- | Table/list data | Skeleton rows or spinner overlay | Data fetching |
823
- | Button action | Spinner inside button + disabled state | Form submit, save, delete |
824
- | File upload | Progress bar | Large uploads |
839
+ | Context | Pattern | When |
840
+ | --------------- | -------------------------------------- | ------------------------- |
841
+ | Page load | Skeleton screen | Initial page navigation |
842
+ | Table/list data | Skeleton rows or spinner overlay | Data fetching |
843
+ | Button action | Spinner inside button + disabled state | Form submit, save, delete |
844
+ | File upload | Progress bar | Large uploads |
825
845
 
826
846
  Debounce loader appearance: do not show any loading indicator for fetches that resolve in < 300ms (avoid flash).
827
847
 
@@ -832,7 +852,8 @@ function EmployeePage() {
832
852
 
833
853
  if (isLoading) return <PageSkeleton />;
834
854
  if (isError) return <ErrorState error={error} onRetry={refetch} />;
835
- if (!data) return <EmptyState message="No employee data found" action={<CreateEmployeeButton />} />;
855
+ if (!data)
856
+ return <EmptyState message="No employee data found" action={<CreateEmployeeButton />} />;
836
857
 
837
858
  return <EmployeeProfile employee={data} />;
838
859
  }
@@ -849,14 +870,22 @@ interface ErrorStateProps {
849
870
 
850
871
  export function ErrorState({ error, onRetry, title = 'Something went wrong' }: ErrorStateProps) {
851
872
  return (
852
- <div role="alert" className="flex flex-col items-center justify-center min-h-[300px] p-6 text-center">
873
+ <div
874
+ role="alert"
875
+ className="flex flex-col items-center justify-center min-h-[300px] p-6 text-center"
876
+ >
853
877
  <AlertTriangle className="w-12 h-12 text-destructive mb-4" />
854
878
  <h3 className="text-lg font-semibold mb-1">{title}</h3>
855
879
  <p className="text-muted-foreground mb-4 max-w-md">
856
- {typeof error === 'string' ? error : error?.message || 'An unexpected error occurred. Please try again.'}
880
+ {typeof error === 'string'
881
+ ? error
882
+ : error?.message || 'An unexpected error occurred. Please try again.'}
857
883
  </p>
858
884
  {onRetry && (
859
- <button onClick={onRetry} className="px-4 py-2 bg-primary text-primary-foreground rounded-md">
885
+ <button
886
+ onClick={onRetry}
887
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
888
+ >
860
889
  Try Again
861
890
  </button>
862
891
  )}
@@ -869,16 +898,16 @@ export function ErrorState({ error, onRetry, title = 'Something went wrong' }: E
869
898
 
870
899
  Every UI component must meet WCAG 2.1 AA standards. The following are **non-negotiable**:
871
900
 
872
- | Requirement | Implementation |
873
- |-------------|---------------|
874
- | Color contrast | Minimum 4.5:1 for text, 3:1 for large text. Verify with axe DevTools. |
875
- | Keyboard navigation | All interactive elements reachable via Tab. Logical tab order. Visible focus ring. |
876
- | Skip link | Skip-to-content link as first focusable element on every page. |
877
- | Screen reader | `aria-label` on icon-only buttons. `aria-describedby` linking inputs to error messages. |
878
- | Form accessibility | Every `<input>` must have an associated `<label>`. Error messages linked via `aria-describedby`. |
879
- | Modals/Dialogs | Focus trap inside modal. Escape to close. `aria-modal="true"`. |
880
- | Images | All `<img>` must have `alt` attribute (descriptive or empty for decorative). |
881
- | Landmarks | Use semantic HTML: `<header>`, `<main>`, `<nav>`, `<footer>`. |
901
+ | Requirement | Implementation |
902
+ | ------------------- | ------------------------------------------------------------------------------------------------ |
903
+ | Color contrast | Minimum 4.5:1 for text, 3:1 for large text. Verify with axe DevTools. |
904
+ | Keyboard navigation | All interactive elements reachable via Tab. Logical tab order. Visible focus ring. |
905
+ | Skip link | Skip-to-content link as first focusable element on every page. |
906
+ | Screen reader | `aria-label` on icon-only buttons. `aria-describedby` linking inputs to error messages. |
907
+ | Form accessibility | Every `<input>` must have an associated `<label>`. Error messages linked via `aria-describedby`. |
908
+ | Modals/Dialogs | Focus trap inside modal. Escape to close. `aria-modal="true"`. |
909
+ | Images | All `<img>` must have `alt` attribute (descriptive or empty for decorative). |
910
+ | Landmarks | Use semantic HTML: `<header>`, `<main>`, `<nav>`, `<footer>`. |
882
911
 
883
912
  **Required tools**: axe DevTools browser extension, eslint-plugin-jsx-a11y (already enforced via ESLint).
884
913
 
@@ -891,6 +920,7 @@ Every UI component must meet WCAG 2.1 AA standards. The following are **non-nego
891
920
  Every data table MUST support sortable column headers, global and per-column filtering, debounced search, persistent sort preference, visible active filters, and a no-results state.
892
921
 
893
922
  Required table features:
923
+
894
924
  - Sort on click for all non-action columns with sort direction indicator
895
925
  - Filter input above table with 300-400ms debounce
896
926
  - Column visibility toggle
@@ -903,6 +933,7 @@ Required table features:
903
933
  #### Jump-to-Top Button (Required on Long Pages)
904
934
 
905
935
  Pages taller than 2x viewport MUST include a fixed back-to-top button:
936
+
906
937
  - Appears after scrolling 500px
907
938
  - Smooth scroll to top
908
939
  - Hidden when at top
@@ -912,6 +943,7 @@ Pages taller than 2x viewport MUST include a fixed back-to-top button:
912
943
  #### Go Back / Go to Previous (Context-Aware Navigation)
913
944
 
914
945
  Every page except the home/landing page MUST provide navigation aids:
946
+
915
947
  - Breadcrumbs showing full path: Home > Reports > Financial > Q1 2026
916
948
  - Back button returning to parent list or previous page
917
949
  - Context-aware return preserving list filters and pagination via query params
@@ -919,6 +951,7 @@ Every page except the home/landing page MUST provide navigation aids:
919
951
  #### Corporate Branding (Required)
920
952
 
921
953
  Every application MUST implement consistent corporate branding:
954
+
922
955
  - Primary logo in header/sidebar, clickable to home, with favicon and Apple touch icon
923
956
  - Brand colors defined in Tailwind config with minimum 4.5:1 contrast ratio
924
957
  - Typography: Inter or system fonts, defined heading scale, 1rem base body text
@@ -928,7 +961,6 @@ Every application MUST implement consistent corporate branding:
928
961
 
929
962
  ---
930
963
 
931
-
932
964
  ---
933
965
 
934
966
  ## 8. Testing Standards
@@ -947,12 +979,12 @@ Every application MUST implement consistent corporate branding:
947
979
 
948
980
  ### 8.2 Coverage Requirements
949
981
 
950
- | Level | Minimum Coverage | Notes |
951
- |-------|-----------------|-------|
952
- | Line | 80% | Enforced in CI pipeline |
953
- | Branch | 75% | Enforced in CI pipeline |
954
- | New Code | 90% | PR checks must pass |
955
- | Critical Paths | 100% | Auth, payments, data export |
982
+ | Level | Minimum Coverage | Notes |
983
+ | -------------- | ---------------- | --------------------------- |
984
+ | Line | 80% | Enforced in CI pipeline |
985
+ | Branch | 75% | Enforced in CI pipeline |
986
+ | New Code | 90% | PR checks must pass |
987
+ | Critical Paths | 100% | Auth, payments, data export |
956
988
 
957
989
  ### 8.3 Test Structure
958
990
 
@@ -1011,6 +1043,7 @@ main # Production-ready code
1011
1043
  **Types**: feat, fix, security, perf, docs, style, refactor, test, chore
1012
1044
 
1013
1045
  **Examples**:
1046
+
1014
1047
  ```
1015
1048
  feat(auth): add JWT refresh token flow
1016
1049
  fix(profile): resolve avatar upload timeout
@@ -1020,13 +1053,14 @@ chore(deps): upgrade React to v19
1020
1053
 
1021
1054
  ### 9.3 Git Hooks (Husky)
1022
1055
 
1023
- | Hook | Action | Tool |
1024
- |------|--------|------|
1025
- | pre-commit | Lint staged files | lint-staged |
1026
- | commit-msg | Validate commit message | commitlint |
1027
- | pre-push | Run lint + modularization + type check + tests | eslint + check-modularization + tsc + vitest |
1056
+ | Hook | Action | Tool |
1057
+ | ---------- | ---------------------------------------------- | -------------------------------------------- |
1058
+ | pre-commit | Lint staged files | lint-staged |
1059
+ | commit-msg | Validate commit message | commitlint |
1060
+ | pre-push | Run lint + modularization + type check + tests | eslint + check-modularization + tsc + vitest |
1028
1061
 
1029
1062
  **lint-staged config**:
1063
+
1030
1064
  ```json
1031
1065
  {
1032
1066
  "src/**/*.{ts,tsx}": ["eslint --fix", "prettier --write"],
@@ -1058,9 +1092,11 @@ chore(deps): upgrade React to v19
1058
1092
 
1059
1093
  ```markdown
1060
1094
  ## Description
1095
+
1061
1096
  [Brief description of the change]
1062
1097
 
1063
1098
  ## Type of Change
1099
+
1064
1100
  - [ ] feat: New feature
1065
1101
  - [ ] fix: Bug fix
1066
1102
  - [ ] refactor: Code restructure
@@ -1069,6 +1105,7 @@ chore(deps): upgrade React to v19
1069
1105
  - [ ] chore: Build/tooling
1070
1106
 
1071
1107
  ## Testing Done
1108
+
1072
1109
  - [ ] Unit tests added/updated
1073
1110
  - [ ] Manual testing performed
1074
1111
  - [ ] E2E tests (if applicable)
@@ -1076,6 +1113,7 @@ chore(deps): upgrade React to v19
1076
1113
  ## Screenshots (if UI change)
1077
1114
 
1078
1115
  ## Checklist
1116
+
1079
1117
  - [ ] Code follows project style guidelines
1080
1118
  - [ ] Self-review completed
1081
1119
  - [ ] No breaking changes (or documented)
@@ -1086,31 +1124,37 @@ chore(deps): upgrade React to v19
1086
1124
  ### 10.3 Review Checklist
1087
1125
 
1088
1126
  **Functional**:
1127
+
1089
1128
  - Does the code do what its supposed to?
1090
1129
  - Are edge cases handled (loading, empty, error states)?
1091
1130
  - Are there appropriate tests?
1092
1131
 
1093
1132
  **Architecture**:
1133
+
1094
1134
  - Is the component properly decomposed?
1095
1135
  - Is business logic separated from UI?
1096
1136
  - Are state management decisions appropriate?
1097
1137
 
1098
1138
  **Security**:
1139
+
1099
1140
  - Are user inputs validated and sanitized?
1100
1141
  - Are secrets exposed anywhere?
1101
1142
  - Is authentication/authorization enforced?
1102
1143
 
1103
1144
  **Performance**:
1145
+
1104
1146
  - Are there unnecessary re-renders?
1105
1147
  - Are large lists properly virtualized?
1106
1148
  - Are images optimized?
1107
1149
 
1108
1150
  **Accessibility**:
1151
+
1109
1152
  - Are all interactive elements keyboard-accessible?
1110
1153
  - Do images have alt text?
1111
1154
  - Is color contrast sufficient?
1112
1155
 
1113
1156
  **Lovable Code**:
1157
+
1114
1158
  - Has the generated code been cleaned up (no unused imports, proper types)?
1115
1159
  - Are generated components properly integrated?
1116
1160
 
@@ -1186,7 +1230,7 @@ jobs:
1186
1230
  steps:
1187
1231
  - uses: actions/checkout@v4
1188
1232
  - uses: actions/setup-node@v4
1189
- with: { node-version: "20" }
1233
+ with: { node-version: '20' }
1190
1234
  - uses: pnpm/action-setup@v2
1191
1235
  with: { version: 9 }
1192
1236
 
@@ -1244,11 +1288,11 @@ CMD ["node", "dist/server.js"]
1244
1288
 
1245
1289
  ### 12.2 Environment Strategy
1246
1290
 
1247
- | Environment | Purpose | Deploy Trigger | Replicas |
1248
- |-------------|---------|---------------|----------|
1249
- | Dev | Developer testing | Push to develop | 1 |
1250
- | Staging | QA + UAT | Push to main | 2 |
1251
- | Production | Live | Manual approval | 3+ (auto-scale) |
1291
+ | Environment | Purpose | Deploy Trigger | Replicas |
1292
+ | ----------- | ----------------- | --------------- | --------------- |
1293
+ | Dev | Developer testing | Push to develop | 1 |
1294
+ | Staging | QA + UAT | Push to main | 2 |
1295
+ | Production | Live | Manual approval | 3+ (auto-scale) |
1252
1296
 
1253
1297
  ### 12.3 Deployment Checklist
1254
1298
 
@@ -1267,17 +1311,17 @@ CMD ["node", "dist/server.js"]
1267
1311
 
1268
1312
  ### 13.1 Mandatory Security Measures
1269
1313
 
1270
- | Measure | Implementation | Priority |
1271
- |---------|---------------|----------|
1272
- | HTTPS | Enforced at ingress level | Critical |
1273
- | CSP Headers | Content Security Policy via server headers | Critical |
1274
- | Input Validation | Zod schemas for all user inputs | Critical |
1275
- | XSS Protection | React escaping + DOMPurify for raw HTML | Critical |
1276
- | CSRF Protection | SameSite cookies + CSRF tokens | High |
1277
- | Rate Limiting | API endpoints (especially auth) | High |
1278
- | Dependency Scanning | npm audit / Snyk in CI | High |
1279
- | Secret Scanning | GitLeaks / TruffleHog in CI | Medium |
1280
- | CORS | Whitelist allowed origins | High |
1314
+ | Measure | Implementation | Priority |
1315
+ | ------------------- | ------------------------------------------ | -------- |
1316
+ | HTTPS | Enforced at ingress level | Critical |
1317
+ | CSP Headers | Content Security Policy via server headers | Critical |
1318
+ | Input Validation | Zod schemas for all user inputs | Critical |
1319
+ | XSS Protection | React escaping + DOMPurify for raw HTML | Critical |
1320
+ | CSRF Protection | SameSite cookies + CSRF tokens | High |
1321
+ | Rate Limiting | API endpoints (especially auth) | High |
1322
+ | Dependency Scanning | npm audit / Snyk in CI | High |
1323
+ | Secret Scanning | GitLeaks / TruffleHog in CI | Medium |
1324
+ | CORS | Whitelist allowed origins | High |
1281
1325
 
1282
1326
  ### 13.2 Secrets NEVER Committed
1283
1327
 
@@ -1316,23 +1360,28 @@ Console and debugger rules: `console.log`, `console.debug`, `console.info`, `con
1316
1360
  Allowed only: `console.warn()` and `console.error()`. Use a structured logger instead.
1317
1361
 
1318
1362
  ESLint rules:
1363
+
1319
1364
  ```
1320
1365
  'no-console': ['error', { allow: ['warn', 'error'] }],
1321
1366
  'no-debugger': 'error',
1322
1367
  ```
1323
1368
 
1324
1369
  Structured logger replacement:
1370
+
1325
1371
  ```typescript
1326
1372
  const isDev = import.meta.env.DEV;
1327
1373
 
1328
1374
  export const logger = {
1329
- debug: (...args: unknown[]) => { if (isDev) console.debug('[DEBUG]', ...args); },
1375
+ debug: (...args: unknown[]) => {
1376
+ if (isDev) console.debug('[DEBUG]', ...args);
1377
+ },
1330
1378
  warn: (...args: unknown[]) => console.warn('[WARN]', ...args),
1331
1379
  error: (...args: unknown[]) => console.error('[ERROR]', ...args),
1332
1380
  };
1333
1381
  ```
1334
1382
 
1335
1383
  Production stripping (Vite/Terser):
1384
+
1336
1385
  ```typescript
1337
1386
  build: {
1338
1387
  minify: 'terser',
@@ -1354,6 +1403,7 @@ build: {
1354
1403
  Sensitive fields must NEVER appear in network responses visible in the browser Network tab.
1355
1404
 
1356
1405
  Mask these data types:
1406
+
1357
1407
  - Auth tokens (JWT, session IDs)
1358
1408
  - Passwords, password hashes, salts
1359
1409
  - PII (SSN, phone, address, email)
@@ -1364,15 +1414,30 @@ Mask these data types:
1364
1414
  Strategy 1: Use public DTOs in service layer so the backend only returns safe fields.
1365
1415
 
1366
1416
  Strategy 2: Global response interceptor masking sensitive fields.
1417
+
1367
1418
  ```typescript
1368
1419
  class ApiClient {
1369
1420
  private SENSITIVE_PATTERNS = [
1370
- 'password', 'passwordhash', 'salt',
1371
- 'ssn', 'socialsecurity', 'nationalid',
1372
- 'internalid', 'databaseid', 'legacyid',
1373
- 'creditcardnumber', 'cvv', 'bankaccount',
1374
- 'apikey', 'privatekey', 'token', 'sessionid',
1375
- 'salary', 'income', 'debt', 'revenue',
1421
+ 'password',
1422
+ 'passwordhash',
1423
+ 'salt',
1424
+ 'ssn',
1425
+ 'socialsecurity',
1426
+ 'nationalid',
1427
+ 'internalid',
1428
+ 'databaseid',
1429
+ 'legacyid',
1430
+ 'creditcardnumber',
1431
+ 'cvv',
1432
+ 'bankaccount',
1433
+ 'apikey',
1434
+ 'privatekey',
1435
+ 'token',
1436
+ 'sessionid',
1437
+ 'salary',
1438
+ 'income',
1439
+ 'debt',
1440
+ 'revenue',
1376
1441
  ];
1377
1442
 
1378
1443
  constructor() {
@@ -1384,11 +1449,11 @@ class ApiClient {
1384
1449
 
1385
1450
  private maskSensitiveData(data: unknown): unknown {
1386
1451
  if (typeof data !== 'object' || data === null) return data;
1387
- if (Array.isArray(data)) return data.map(item => this.maskSensitiveData(item));
1452
+ if (Array.isArray(data)) return data.map((item) => this.maskSensitiveData(item));
1388
1453
  const masked: Record<string, unknown> = {};
1389
1454
  for (const [key, value] of Object.entries(data)) {
1390
1455
  const lowerKey = key.toLowerCase();
1391
- if (this.SENSITIVE_PATTERNS.some(p => lowerKey.includes(p))) {
1456
+ if (this.SENSITIVE_PATTERNS.some((p) => lowerKey.includes(p))) {
1392
1457
  masked[key] = '[REDACTED]';
1393
1458
  } else if (typeof value === 'object' && value !== null) {
1394
1459
  masked[key] = this.maskSensitiveData(value);
@@ -1402,6 +1467,7 @@ class ApiClient {
1402
1467
  ```
1403
1468
 
1404
1469
  Strategy 3: Disable caching for sensitive endpoints.
1470
+
1405
1471
  ```typescript
1406
1472
  export const sensitiveService = {
1407
1473
  getFinancialData: () =>
@@ -1412,22 +1478,24 @@ export const sensitiveService = {
1412
1478
  ```
1413
1479
 
1414
1480
  Production verification checklist:
1481
+
1415
1482
  - Open DevTools > Network tab
1416
1483
  - Verify no Authorization header visible (use httpOnly cookies)
1417
1484
  - Verify no passwords, SSNs, or internal IDs in request bodies
1418
1485
  - Verify masked responses show [REDACTED] for sensitive fields
1419
1486
 
1420
-
1421
1487
  ### 13.6 Authorization: RBAC, ABAC, Guards, and DevMode Safety
1422
1488
 
1423
1489
  Required authorization model: RBAC baseline, ABAC for policy restrictions. Enforced in route guards, component guards, and data/service guards. Backend must also enforce policy independently.
1424
1490
 
1425
1491
  Core rules:
1492
+
1426
1493
  - No feature, page, action, button, table row, or API call may be rendered or executed without an authorization check
1427
1494
  - Authorized state MUST NOT be derivable from the UI alone in developer mode
1428
1495
  - Authorization checks MUST be centralized
1429
1496
 
1430
1497
  RBAC structure:
1498
+
1431
1499
  ```tsx
1432
1500
  export enum Role {
1433
1501
  SUPER_ADMIN = 'super_admin',
@@ -1448,6 +1516,7 @@ export enum Permission {
1448
1516
  ```
1449
1517
 
1450
1518
  ABAC structure:
1519
+
1451
1520
  ```tsx
1452
1521
  export type AbacContext = {
1453
1522
  role: Role;
@@ -1475,13 +1544,14 @@ export const canAccessResource: AbacPolicy = (ctx, action, resource) => {
1475
1544
  ```
1476
1545
 
1477
1546
  Route guards:
1547
+
1478
1548
  ```tsx
1479
1549
  export function RequireAuth({ roles, permissions, abacCheck, children }: RequireAuthProps) {
1480
1550
  const auth = useAuth();
1481
1551
  const location = useLocation();
1482
- if (!auth.isAuthenticated) return <Navigate to='/login' state={{ from: location }} replace />;
1552
+ if (!auth.isAuthenticated) return <Navigate to="/login" state={{ from: location }} replace />;
1483
1553
  const hasRole = roles ? roles.includes(auth.role) : true;
1484
- const hasPermission = permissions ? permissions.every(p => auth.permissions.includes(p)) : true;
1554
+ const hasPermission = permissions ? permissions.every((p) => auth.permissions.includes(p)) : true;
1485
1555
  const abacPass = abacCheck ? abacCheck(auth.abacContext) : true;
1486
1556
  if (!hasRole || !hasPermission || !abacPass) return <Forbidden />;
1487
1557
  return <>{children}</>;
@@ -1489,11 +1559,12 @@ export function RequireAuth({ roles, permissions, abacCheck, children }: Require
1489
1559
  ```
1490
1560
 
1491
1561
  Component guards:
1562
+
1492
1563
  ```tsx
1493
1564
  export function Can({ roles, permissions, abacCheck, fallback = null, children }: CanProps) {
1494
1565
  const auth = useAuth();
1495
1566
  const hasRole = roles ? roles.includes(auth.role) : true;
1496
- const hasPermission = permissions ? permissions.some(p => auth.permissions.includes(p)) : true;
1567
+ const hasPermission = permissions ? permissions.some((p) => auth.permissions.includes(p)) : true;
1497
1568
  const abacPass = abacCheck ? abacCheck(auth.abacContext) : true;
1498
1569
  if (!hasRole || !hasPermission || !abacPass) return <>{fallback}</>;
1499
1570
  return <>{children}</>;
@@ -1501,6 +1572,7 @@ export function Can({ roles, permissions, abacCheck, fallback = null, children }
1501
1572
  ```
1502
1573
 
1503
1574
  Data guards:
1575
+
1504
1576
  ```tsx
1505
1577
  export function useAuthorizedUser(id: string) {
1506
1578
  const auth = useAuth();
@@ -1508,7 +1580,13 @@ export function useAuthorizedUser(id: string) {
1508
1580
  queryKey: ['user', id],
1509
1581
  queryFn: async () => {
1510
1582
  const data = await usersService.getById(id);
1511
- if (!canAccessResource(auth.abacContext, 'users:read', { type: 'user', id, tenantId: data.tenantId })) {
1583
+ if (
1584
+ !canAccessResource(auth.abacContext, 'users:read', {
1585
+ type: 'user',
1586
+ id,
1587
+ tenantId: data.tenantId,
1588
+ })
1589
+ ) {
1512
1590
  throw new Error('Forbidden');
1513
1591
  }
1514
1592
  return data;
@@ -1518,11 +1596,13 @@ export function useAuthorizedUser(id: string) {
1518
1596
  ```
1519
1597
 
1520
1598
  DevMode safety:
1599
+
1521
1600
  - Dev-only endpoints, hidden buttons, debug flags MUST NOT bypass authorization
1522
1601
  - Hardcoded ADMIN=true query params, secret URL paths MUST be treated as security bugs
1523
1602
  - Impersonation and elevated roles MUST be logged and auditable
1524
1603
 
1525
1604
  ESLint enforcement:
1605
+
1526
1606
  ```
1527
1607
  no-restricted-syntax: ['error', {
1528
1608
  selector: "MemberExpression[property.name='role']",
@@ -1531,30 +1611,32 @@ no-restricted-syntax: ['error', {
1531
1611
  ```
1532
1612
 
1533
1613
  Authorization code review checklist:
1614
+
1534
1615
  - Every protected route uses RequireAuth
1535
1616
  - Every sensitive action uses Can
1536
1617
  - ABAC policies for tenant, ownership, region, or time restrictions
1537
1618
  - No inline role/permission checks in JSX
1538
1619
  - No hidden dev paths bypassing authorization
1539
1620
  - No mock authorization used outside tests
1621
+
1540
1622
  ## 14. Monitoring & Observability
1541
1623
 
1542
1624
  ### 14.1 Required Instrumentation
1543
1625
 
1544
- | Tool | Purpose | Implementation |
1545
- |------|---------|---------------|
1546
- | Sentry | Error tracking (frontend) | @sentry/react |
1547
- | Lighthouse CI | Performance budget | GitHub Action |
1548
- | Console Logging | Structured logging | No console.log in production |
1626
+ | Tool | Purpose | Implementation |
1627
+ | --------------- | ------------------------- | ---------------------------- |
1628
+ | Sentry | Error tracking (frontend) | @sentry/react |
1629
+ | Lighthouse CI | Performance budget | GitHub Action |
1630
+ | Console Logging | Structured logging | No console.log in production |
1549
1631
 
1550
1632
  ### 14.2 Performance Budget
1551
1633
 
1552
- | Metric | Budget | Enforcement |
1553
- |--------|--------|-------------|
1554
- | First Contentful Paint (FCP) | < 1.5s | Lighthouse CI |
1555
- | Largest Contentful Paint (LCP) | < 2.5s | Lighthouse CI |
1556
- | Time to Interactive (TTI) | < 3.5s | Lighthouse CI |
1557
- | Bundle Size (initial) | < 200KB gzipped | size-limit package |
1634
+ | Metric | Budget | Enforcement |
1635
+ | ------------------------------ | --------------- | ------------------ |
1636
+ | First Contentful Paint (FCP) | < 1.5s | Lighthouse CI |
1637
+ | Largest Contentful Paint (LCP) | < 2.5s | Lighthouse CI |
1638
+ | Time to Interactive (TTI) | < 3.5s | Lighthouse CI |
1639
+ | Bundle Size (initial) | < 200KB gzipped | size-limit package |
1558
1640
 
1559
1641
  ---
1560
1642
 
@@ -1562,21 +1644,23 @@ Authorization code review checklist:
1562
1644
 
1563
1645
  ### 15.1 Minimum Required Docs
1564
1646
 
1565
- | File | Purpose | Required |
1566
- |------|---------|----------|
1567
- | README.md | Project overview, setup, commands | Yes |
1568
- | CONTRIBUTING.md | How to contribute, coding standards | Yes |
1569
- | .env.example | All required environment variables | Yes |
1570
- | docs/architecture.md | High-level architecture decisions | For complex projects |
1571
- | docs/onboarding.md | Setup checklist for new developers | For monorepos |
1647
+ | File | Purpose | Required |
1648
+ | -------------------- | ----------------------------------- | -------------------- |
1649
+ | README.md | Project overview, setup, commands | Yes |
1650
+ | CONTRIBUTING.md | How to contribute, coding standards | Yes |
1651
+ | .env.example | All required environment variables | Yes |
1652
+ | docs/architecture.md | High-level architecture decisions | For complex projects |
1653
+ | docs/onboarding.md | Setup checklist for new developers | For monorepos |
1572
1654
 
1573
1655
  ### 15.2 README Template
1574
1656
 
1575
- ```markdown
1657
+ ````markdown
1576
1658
  # Project Name
1659
+
1577
1660
  [Brief description]
1578
1661
 
1579
1662
  ## Quick Start
1663
+
1580
1664
  ```bash
1581
1665
  git clone <repo>
1582
1666
  cd project
@@ -1584,8 +1668,10 @@ pnpm install
1584
1668
  cp .env.example .env
1585
1669
  pnpm dev
1586
1670
  ```
1671
+ ````
1587
1672
 
1588
1673
  ## Available Scripts
1674
+
1589
1675
  | Command | Description |
1590
1676
  | pnpm dev | Start development server |
1591
1677
  | pnpm build | Build for production |
@@ -1593,8 +1679,10 @@ pnpm dev
1593
1679
  | pnpm lint | Lint code |
1594
1680
 
1595
1681
  ## Environment Variables
1682
+
1596
1683
  [Table of required env vars]
1597
- ```
1684
+
1685
+ ````
1598
1686
 
1599
1687
  ---
1600
1688
 
@@ -1611,7 +1699,7 @@ pnpm lint # Lint all files
1611
1699
  pnpm format # Format with Prettier
1612
1700
  pnpm check-types # TypeScript type checking
1613
1701
  pnpm audit # Security audit
1614
- ```
1702
+ ````
1615
1703
 
1616
1704
  ### 16.2 File Naming Reference
1617
1705
 
@@ -1627,19 +1715,19 @@ constants/auth.ts # Constants
1627
1715
 
1628
1716
  ### 16.3 Key Config Files
1629
1717
 
1630
- | File | Purpose |
1631
- |------|---------|
1632
- | tsconfig.json | TypeScript configuration |
1633
- | vite.config.ts | Vite build configuration |
1634
- | eslint.config.js | ESLint configuration |
1635
- | .prettierrc | Prettier formatting rules |
1636
- | .husky/pre-commit | Pre-commit hook (lint-staged) |
1637
- | commitlint.config.js | Conventional commit rules |
1638
- | .github/workflows/ci.yml | CI pipeline |
1639
- | .github/workflows/cd.yml | CD pipeline |
1640
- | Dockerfile | Container build |
1641
- | docker-compose.yml | Local development |
1642
- | .env.example | Documented env variables |
1718
+ | File | Purpose |
1719
+ | ------------------------ | ----------------------------- |
1720
+ | tsconfig.json | TypeScript configuration |
1721
+ | vite.config.ts | Vite build configuration |
1722
+ | eslint.config.js | ESLint configuration |
1723
+ | .prettierrc | Prettier formatting rules |
1724
+ | .husky/pre-commit | Pre-commit hook (lint-staged) |
1725
+ | commitlint.config.js | Conventional commit rules |
1726
+ | .github/workflows/ci.yml | CI pipeline |
1727
+ | .github/workflows/cd.yml | CD pipeline |
1728
+ | Dockerfile | Container build |
1729
+ | docker-compose.yml | Local development |
1730
+ | .env.example | Documented env variables |
1643
1731
 
1644
1732
  ### 16.4 Package.json Scripts Template
1645
1733
 
@@ -1805,8 +1893,7 @@ import type { EmployeeBio } from '@/types';
1805
1893
  const ENDPOINT = '/employees';
1806
1894
 
1807
1895
  export const employeeBioDetailService = {
1808
- getBio: (employeeId: string) =>
1809
- apiClient.get<EmployeeBio>(`${ENDPOINT}/${employeeId}/bio`),
1896
+ getBio: (employeeId: string) => apiClient.get<EmployeeBio>(`${ENDPOINT}/${employeeId}/bio`),
1810
1897
  updateBio: (employeeId: string, data: Partial<EmployeeBio>) =>
1811
1898
  apiClient.put<EmployeeBio>(`${ENDPOINT}/${employeeId}/bio`, data),
1812
1899
  };
@@ -1852,8 +1939,8 @@ export function useUsers(page = 1) {
1852
1939
  return useQuery({
1853
1940
  queryKey: userKeys.list({ page }),
1854
1941
  queryFn: () => usersService.getAll(page),
1855
- staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
1856
- gcTime: 30 * 60 * 1000, // Keep cache for 30 minutes
1942
+ staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
1943
+ gcTime: 30 * 60 * 1000, // Keep cache for 30 minutes
1857
1944
  refetchOnWindowFocus: true,
1858
1945
  retry: 3,
1859
1946
  });
@@ -1871,15 +1958,16 @@ export function useCreateUser() {
1871
1958
 
1872
1959
  ### 17.6 Client-Side Caching Strategy
1873
1960
 
1874
- | Cache Layer | Technology | Use Case | Persistence |
1875
- |-------------|-----------|----------|-------------|
1876
- | Server State | TanStack React Query | API caching, dedup, background refetch | In-memory |
1877
- | Persistent Storage | localStorage | User preferences, theme, auth tokens | Disk |
1878
- | Large Data | IndexedDB via Dexie.js | Offline data, large datasets | Disk |
1879
- | Route Prefetch | Router loaders | Anticipatory data loading | In-memory |
1880
- | Service Worker | Workbox | Full offline support | Cache Storage API |
1961
+ | Cache Layer | Technology | Use Case | Persistence |
1962
+ | ------------------ | ---------------------- | -------------------------------------- | ----------------- |
1963
+ | Server State | TanStack React Query | API caching, dedup, background refetch | In-memory |
1964
+ | Persistent Storage | localStorage | User preferences, theme, auth tokens | Disk |
1965
+ | Large Data | IndexedDB via Dexie.js | Offline data, large datasets | Disk |
1966
+ | Route Prefetch | Router loaders | Anticipatory data loading | In-memory |
1967
+ | Service Worker | Workbox | Full offline support | Cache Storage API |
1881
1968
 
1882
1969
  **Rules**:
1970
+
1883
1971
  - Never cache sensitive data (tokens, PII) in localStorage without encryption
1884
1972
  - Always set staleTime / gcTime for API responses
1885
1973
  - Invalidate caches on mutations (create, update, delete)
@@ -1893,6 +1981,7 @@ export function useCreateUser() {
1893
1981
  ### 18.1 The Golden Rule
1894
1982
 
1895
1983
  > **Hooks are for behavior, not data.** If data comes from an API, use React Query + your service layer. Reserve hooks for:
1984
+ >
1896
1985
  > - Connecting UI to data (React Query hooks)
1897
1986
  > - Encapsulating complex browser APIs (resize, scroll, geolocation)
1898
1987
  > - Reusable form logic
@@ -1901,12 +1990,14 @@ export function useCreateUser() {
1901
1990
  ### 18.2 useEffect -- When and How
1902
1991
 
1903
1992
  **DO use useEffect for**:
1993
+
1904
1994
  - Synchronizing with external systems (WebSocket, browser APIs, analytics)
1905
1995
  - Setting up subscriptions with proper cleanup
1906
1996
  - Managing non-React state (syncing to localStorage)
1907
1997
  - Imperative operations (focus management, scroll restoration)
1908
1998
 
1909
1999
  **DO NOT use useEffect for**:
2000
+
1910
2001
  - Fetching data on mount --> Use React Query
1911
2002
  - Deriving state from props --> Use useMemo
1912
2003
  - Responding to user actions --> Use event handlers directly
@@ -1921,20 +2012,28 @@ useEffect(() => {
1921
2012
  }, [url]);
1922
2013
 
1923
2014
  // BAD: Fetching data manually (use React Query instead)
1924
- useEffect(() => { fetch('/api/users').then(r => r.json()).then(setUsers); }, []);
2015
+ useEffect(() => {
2016
+ fetch('/api/users')
2017
+ .then((r) => r.json())
2018
+ .then(setUsers);
2019
+ }, []);
1925
2020
 
1926
2021
  // BAD: Deriving state from props
1927
- useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]);
2022
+ useEffect(() => {
2023
+ setFullName(`${firstName} ${lastName}`);
2024
+ }, [firstName, lastName]);
1928
2025
  // FIX: const fullName = `${firstName} ${lastName}`;
1929
2026
  ```
1930
2027
 
1931
2028
  ### 18.3 useMemo -- When to Memoize
1932
2029
 
1933
2030
  **DO use useMemo for**:
2031
+
1934
2032
  - Expensive computations (filtering/sorting large lists, complex data transformation)
1935
2033
  - Creating stable object references for child component props (prevents re-renders)
1936
2034
 
1937
2035
  **DO NOT use useMemo for**:
2036
+
1938
2037
  - Simple calculations (firstName + lastName)
1939
2038
  - Values used once or in a single render
1940
2039
  - Premature optimization -- measure first, then memoize
@@ -1942,9 +2041,7 @@ useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastNa
1942
2041
  ```typescript
1943
2042
  // GOOD: Expensive computation memoized
1944
2043
  const filteredUsers = useMemo(() => {
1945
- return users
1946
- .filter((u) => u.role === selectedRole)
1947
- .sort((a, b) => a.name.localeCompare(b.name));
2044
+ return users.filter((u) => u.role === selectedRole).sort((a, b) => a.name.localeCompare(b.name));
1948
2045
  }, [users, selectedRole]);
1949
2046
 
1950
2047
  // GOOD: Stable object reference for child
@@ -1958,11 +2055,13 @@ const greeting = useMemo(() => `Hello, ${name}!`, [name]);
1958
2055
  ### 18.4 useCallback -- When to Memoize Functions
1959
2056
 
1960
2057
  **DO use useCallback for**:
2058
+
1961
2059
  - Callback props passed to memoized children (React.memo)
1962
2060
  - Functions in useEffect dependency arrays
1963
2061
  - Functions passed to context providers
1964
2062
 
1965
2063
  **DO NOT use useCallback for**:
2064
+
1966
2065
  - Event handlers on native HTML elements (<button onClick>)
1967
2066
  - Functions used in a single parent component with no memoized children
1968
2067
  - Premature optimization
@@ -2028,6 +2127,7 @@ function Dashboard() {
2028
2127
  ```
2029
2128
 
2030
2129
  **When to use**:
2130
+
2031
2131
  - Search fields with large result sets
2032
2132
  - Tab switching with heavy content
2033
2133
  - Chart/table rendering blocking the UI thread
@@ -2068,18 +2168,18 @@ function SearchUsers() {
2068
2168
 
2069
2169
  ### 18.7 Performance Decision Guide
2070
2170
 
2071
- | Situation | Solution | Why |
2072
- |-----------|----------|-----|
2073
- | Fetching API data | React Query (useQuery) | Built-in caching, dedup, retry, stale-while-revalidate |
2074
- | Mutating server data | React Query (useMutation) | Optimistic updates, cache invalidation, rollback |
2075
- | Expensive calculation on props change | useMemo | Only re-computes when dependencies change |
2076
- | Stable callback for memoized child | useCallback | Prevents unnecessary child re-renders |
2077
- | Debouncing user input | Custom useDebounce hook | Reduces API calls during rapid typing |
2078
- | Non-urgent state update | useTransition | Keeps UI responsive during heavy renders |
2079
- | Deferring slow component re-render | useDeferredValue | Shows stale content while new renders |
2080
- | Subscribing to browser API | useEffect with cleanup | Proper lifecycle management |
2081
- | Form state | React Hook Form | Built-in validation, minimal re-renders |
2082
- | Global state | Zustand | No providers, selective subscriptions |
2171
+ | Situation | Solution | Why |
2172
+ | ------------------------------------- | ------------------------- | ------------------------------------------------------ |
2173
+ | Fetching API data | React Query (useQuery) | Built-in caching, dedup, retry, stale-while-revalidate |
2174
+ | Mutating server data | React Query (useMutation) | Optimistic updates, cache invalidation, rollback |
2175
+ | Expensive calculation on props change | useMemo | Only re-computes when dependencies change |
2176
+ | Stable callback for memoized child | useCallback | Prevents unnecessary child re-renders |
2177
+ | Debouncing user input | Custom useDebounce hook | Reduces API calls during rapid typing |
2178
+ | Non-urgent state update | useTransition | Keeps UI responsive during heavy renders |
2179
+ | Deferring slow component re-render | useDeferredValue | Shows stale content while new renders |
2180
+ | Subscribing to browser API | useEffect with cleanup | Proper lifecycle management |
2181
+ | Form state | React Hook Form | Built-in validation, minimal re-renders |
2182
+ | Global state | Zustand | No providers, selective subscriptions |
2083
2183
 
2084
2184
  ### 18.8 Common Anti-Patterns to Avoid
2085
2185
 
@@ -2143,6 +2243,7 @@ Is the UI freezing during state updates?
2143
2243
  |
2144
2244
  You probably don't need any hook. Write plain JS/TS.
2145
2245
  ```
2246
+
2146
2247
  ### 18.10 Form Validation & Submission Standards
2147
2248
 
2148
2249
  Every form must use React Hook Form with Zod validation. The schema must be co-located with the form component.
@@ -2212,6 +2313,7 @@ export function EmployeeBioForm() {
2212
2313
  ```
2213
2314
 
2214
2315
  **Rules**:
2316
+
2215
2317
  - Zod schema co-located with form (not in a separate `types/` file)
2216
2318
  - Server errors mapped to field-level via `setError` (not shown as generic toast)
2217
2319
  - Unsaved changes warning via `beforeunload` when `isDirty`
@@ -2227,18 +2329,18 @@ export function EmployeeBioForm() {
2227
2329
 
2228
2330
  ### 19.1 Core Principle: Automation Over Manual Review
2229
2331
 
2230
- No rule is valuable if it relies on humans remembering to check it. Every standard in this document must have automated enforcement.
2332
+ No rule is valuable if it relies on humans remembering to check it. Every standard in this document must have automated enforcement.
2231
2333
 
2232
2334
  **Note on Package Managers**: While developers are free to use any package manager (npm, yarn, bun) for local development, all automated enforcement and CI/CD pipelines are optimized for **pnpm**. Ensure `pnpm-lock.yaml` is kept up to date.
2233
2335
 
2234
2336
  **Mandatory Enforcement Layers**:
2235
2337
 
2236
- | Layer | Tool | When | Purpose |
2237
- |-------|------|------|---------|
2238
- | L1: Editor | ESLint + Prettier | Every save | Immediate feedback while coding |
2239
- | L2: Pre-commit Hook | Husky + lint-staged | Every `git commit` | Block bad code before it enters history |
2240
- | L3: CI Pipeline | GitHub Actions | Every PR / push | Enforce before merge, across all contributors |
2241
- | L4: Code Review | Human review | Every PR | Architecture, logic, edge cases automation cannot catch |
2338
+ | Layer | Tool | When | Purpose |
2339
+ | ------------------- | ------------------- | ------------------ | ------------------------------------------------------- |
2340
+ | L1: Editor | ESLint + Prettier | Every save | Immediate feedback while coding |
2341
+ | L2: Pre-commit Hook | Husky + lint-staged | Every `git commit` | Block bad code before it enters history |
2342
+ | L3: CI Pipeline | GitHub Actions | Every PR / push | Enforce before merge, across all contributors |
2343
+ | L4: Code Review | Human review | Every PR | Architecture, logic, edge cases automation cannot catch |
2242
2344
 
2243
2345
  ### 19.2 Required Husky + lint-staged Setup
2244
2346
 
@@ -2302,10 +2404,7 @@ The `check-modularization` script (see Step 7) validates folder structure and ba
2302
2404
  // package.json
2303
2405
  {
2304
2406
  "lint-staged": {
2305
- "src/**/*.{ts,tsx}": [
2306
- "eslint --fix --max-warnings 0",
2307
- "prettier --write"
2308
- ],
2407
+ "src/**/*.{ts,tsx}": ["eslint --fix --max-warnings 0", "prettier --write"],
2309
2408
  "src/**/*.{css,scss}": ["prettier --write"],
2310
2409
  "*.{json,md,yaml,yml}": ["prettier --write"]
2311
2410
  }
@@ -2378,12 +2477,12 @@ rules: {
2378
2477
 
2379
2478
  **The `--no-verify` policy**:
2380
2479
 
2381
- | Action | Consequence |
2382
- |--------|-------------|
2383
- | `git commit --no-verify` | Commit lands locally, but CI catches all skipped checks on push/PR |
2384
- | `git push --no-verify` | Push succeeds, but CI pre-push checks run anyway; PR blocked if failing |
2385
- | Tampering with `.husky/` files | Caught in code review; `.husky/` is committed and reviewed |
2386
- | Removing hooks from `package.json` | CI `pnpm install` re-installs husky; hooks re-created |
2480
+ | Action | Consequence |
2481
+ | ---------------------------------- | ----------------------------------------------------------------------- |
2482
+ | `git commit --no-verify` | Commit lands locally, but CI catches all skipped checks on push/PR |
2483
+ | `git push --no-verify` | Push succeeds, but CI pre-push checks run anyway; PR blocked if failing |
2484
+ | Tampering with `.husky/` files | Caught in code review; `.husky/` is committed and reviewed |
2485
+ | Removing hooks from `package.json` | CI `pnpm install` re-installs husky; hooks re-created |
2387
2486
 
2388
2487
  > **TL;DR**: `--no-verify` delays validation from commit-time to CI-time. The checks still run and the PR still gets blocked. There is no path to merge that bypasses these standards.
2389
2488
 
@@ -2430,12 +2529,22 @@ function logWarning(msg) {
2430
2529
 
2431
2530
  // Helper: check if a path is a directory
2432
2531
  function isDirectory(path) {
2433
- try { return statSync(path).isDirectory(); } catch { return false; }
2532
+ try {
2533
+ return statSync(path).isDirectory();
2534
+ } catch {
2535
+ return false;
2536
+ }
2434
2537
  }
2435
2538
 
2436
2539
  // Helper: check if a file is a TypeScript file
2437
2540
  function isTsFile(filename) {
2438
- return /\.(ts|tsx)$/.test(filename) && !filename.endsWith('.test.ts') && !filename.endsWith('.test.tsx') && !filename.endsWith('.spec.ts') && !filename.endsWith('.spec.tsx');
2541
+ return (
2542
+ /\.(ts|tsx)$/.test(filename) &&
2543
+ !filename.endsWith('.test.ts') &&
2544
+ !filename.endsWith('.test.tsx') &&
2545
+ !filename.endsWith('.spec.ts') &&
2546
+ !filename.endsWith('.spec.tsx')
2547
+ );
2439
2548
  }
2440
2549
 
2441
2550
  console.log('\n🔍 Checking modularization compliance...\n');
@@ -2460,9 +2569,11 @@ for (const dirName of MODULARIZED_DIRS) {
2460
2569
  } else {
2461
2570
  // Verify barrel exports at least one member
2462
2571
  const barrelContent = readdirSync(entryPath);
2463
- const tsFiles = barrelContent.filter(f => isTsFile(f) && f !== BARREL_FILE);
2572
+ const tsFiles = barrelContent.filter((f) => isTsFile(f) && f !== BARREL_FILE);
2464
2573
  if (tsFiles.length === 0) {
2465
- logWarning(`${dirName}/${entry}/${BARREL_FILE} exists but no implementation files found in this domain`);
2574
+ logWarning(
2575
+ `${dirName}/${entry}/${BARREL_FILE} exists but no implementation files found in this domain`
2576
+ );
2466
2577
  }
2467
2578
  }
2468
2579
 
@@ -2470,8 +2581,15 @@ for (const dirName of MODULARIZED_DIRS) {
2470
2581
  const subEntries = readdirSync(entryPath);
2471
2582
  for (const subEntry of subEntries) {
2472
2583
  const subPath = join(entryPath, subEntry);
2473
- if (subEntry !== 'test' && subEntry !== '__tests__' && subEntry !== '__mocks__' && isDirectory(subPath)) {
2474
- logWarning(`${dirName}/${entry}/ should not contain nested subdirectory "${subEntry}/" -- keep domains flat`);
2584
+ if (
2585
+ subEntry !== 'test' &&
2586
+ subEntry !== '__tests__' &&
2587
+ subEntry !== '__mocks__' &&
2588
+ isDirectory(subPath)
2589
+ ) {
2590
+ logWarning(
2591
+ `${dirName}/${entry}/ should not contain nested subdirectory "${subEntry}/" -- keep domains flat`
2592
+ );
2475
2593
  }
2476
2594
  }
2477
2595
  } else if (isTsFile(entry)) {
@@ -2479,8 +2597,8 @@ for (const dirName of MODULARIZED_DIRS) {
2479
2597
  if (!allowedRoot.includes(entry)) {
2480
2598
  logError(
2481
2599
  `Flat file "${dirName}/${entry}" is not allowed at the root level. ` +
2482
- `Move it to a domain subdirectory (e.g., ${dirName}/${entry.replace(/\.(ts|tsx)$/, '')}/${entry}). ` +
2483
- `Allowed root files: ${allowedRoot.join(', ') || 'none'}`
2600
+ `Move it to a domain subdirectory (e.g., ${dirName}/${entry.replace(/\.(ts|tsx)$/, '')}/${entry}). ` +
2601
+ `Allowed root files: ${allowedRoot.join(', ') || 'none'}`
2484
2602
  );
2485
2603
  }
2486
2604
  }
@@ -2496,7 +2614,9 @@ try {
2496
2614
  logError('ESLint found violations (may include deep imports bypassing barrel files)');
2497
2615
  }
2498
2616
 
2499
- console.log(`\n${errors === 0 ? '✅' : '❌'} Modularization check complete. Errors: ${errors}, Warnings: ${warnings}\n`);
2617
+ console.log(
2618
+ `\n${errors === 0 ? '✅' : '❌'} Modularization check complete. Errors: ${errors}, Warnings: ${warnings}\n`
2619
+ );
2500
2620
 
2501
2621
  if (errors > 0) {
2502
2622
  console.error('Fix the errors above and try again.\n');
@@ -2509,6 +2629,7 @@ if (warnings > 0) {
2509
2629
  ```
2510
2630
 
2511
2631
  **What this enforces at commit time**:
2632
+
2512
2633
  - ESLint auto-fixes what it can, then fails hard on remaining errors (`--max-warnings 0`)
2513
2634
  - Prettier formats all staged files
2514
2635
  - Commitlint rejects non-conventional commit messages
@@ -2524,17 +2645,7 @@ export default {
2524
2645
  rules: {
2525
2646
  'type-enum': [
2526
2647
  'error',
2527
- [
2528
- 'feat',
2529
- 'fix',
2530
- 'security',
2531
- 'perf',
2532
- 'docs',
2533
- 'style',
2534
- 'refactor',
2535
- 'test',
2536
- 'chore',
2537
- ],
2648
+ ['feat', 'fix', 'security', 'perf', 'docs', 'style', 'refactor', 'test', 'chore'],
2538
2649
  ],
2539
2650
  'subject-max-length': [2, 'always', 100],
2540
2651
  'body-max-line-length': [2, 'always', 100],
@@ -2791,11 +2902,11 @@ console.log(chalk.green('\nAll pre-commit checks passed!'));
2791
2902
 
2792
2903
  ### 19.9 What Each Layer Catches
2793
2904
 
2794
- | Layer | Catches | Cannot Catch |
2795
- |-------|---------|--------------|
2796
- | L1 Editor | Typos, bad names, bad formatting | Logic bugs, architecture flaws |
2797
- | L2 Pre-commit | Same as L1 + blocks commit | Business logic errors |
2798
- | L3 CI Pipeline | Everything automated can check | Business logic correctness |
2905
+ | Layer | Catches | Cannot Catch |
2906
+ | -------------- | ---------------------------------- | --------------------------------- |
2907
+ | L1 Editor | Typos, bad names, bad formatting | Logic bugs, architecture flaws |
2908
+ | L2 Pre-commit | Same as L1 + blocks commit | Business logic errors |
2909
+ | L3 CI Pipeline | Everything automated can check | Business logic correctness |
2799
2910
  | L4 Code Review | Architecture, security, edge cases | Naming that already passed ESLint |
2800
2911
 
2801
2912
  ### 19.10 Enforcement Checklist for New Projects
@@ -2826,6 +2937,7 @@ When setting up a new project, verify:
2826
2937
  > **This is a living document.** It should be reviewed and updated quarterly by the frontend chapter lead and architecture team. Any deviation from these standards must be documented in an Architecture Decision Record (ADR).
2827
2938
 
2828
2939
  ---
2940
+
2829
2941
  ## Appendix C. Frontend Cost & Pricing Reference (June 2026)
2830
2942
 
2831
2943
  > Use this to estimate implementation and operational costs when proposing a new project or staffing decisions. Prices are approximations for mid-market commercial engagements and can vary by vendor, region, and contract length.
@@ -2844,39 +2956,39 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
2844
2956
 
2845
2957
  ### C.1 Estimated Annual Costs by Project Size
2846
2958
 
2847
- | Project Size | Frontend Devs | Backend Devs | DevOps | QA | Tools & Licenses | Infrastructure | Total Est. Annual |
2848
- |---|---|---|---|---|---|---|---|
2849
- | Small (MVP/closed beta) | 1 | 1 | 0.25 | 0.5 | ~USD 2,000 | ~USD 1,500 | ~USD 160,000 |
2850
- | Medium (production SaaS) | 2 | 2 | 0.5 | 1 | ~USD 5,000 | ~USD 3,500 | ~USD 350,000 |
2851
- | Large (enterprise/multi-tenant) | 4 | 4 | 1 | 2 | ~USD 12,000 | ~USD 8,000 | ~USD 800,000 |
2852
- | Enterprise (regulated/high-scale) | 6+ | 6+ | 2+ | 3+ | ~USD 25,000+ | ~USD 20,000+ | ~USD 1,600,000+ |
2959
+ | Project Size | Frontend Devs | Backend Devs | DevOps | QA | Tools & Licenses | Infrastructure | Total Est. Annual |
2960
+ | --------------------------------- | ------------- | ------------ | ------ | --- | ---------------- | -------------- | ----------------- |
2961
+ | Small (MVP/closed beta) | 1 | 1 | 0.25 | 0.5 | ~USD 2,000 | ~USD 1,500 | ~USD 160,000 |
2962
+ | Medium (production SaaS) | 2 | 2 | 0.5 | 1 | ~USD 5,000 | ~USD 3,500 | ~USD 350,000 |
2963
+ | Large (enterprise/multi-tenant) | 4 | 4 | 1 | 2 | ~USD 12,000 | ~USD 8,000 | ~USD 800,000 |
2964
+ | Enterprise (regulated/high-scale) | 6+ | 6+ | 2+ | 3+ | ~USD 25,000+ | ~USD 20,000+ | ~USD 1,600,000+ |
2853
2965
 
2854
2966
  ### C.2 Tooling & License Costs (Annual)
2855
2967
 
2856
- | Tool / Service | Purpose | Typical Tier | Est. Cost | Notes |
2857
- |---|---|---|---|---|
2858
- | GitHub Enterprise / Teams | Repo hosting, Actions minutes, GHCR, Environments | Team: USD 4/user/mo; Enterprise: USD 21/user/mo | USD 2,400 – USD 25,000/yr | Public repos have free Actions minutes; private minutes are billed beyond the free tier (2,000 min/mo). |
2859
- | Sentry (Error Tracking) | Frontend & backend error monitoring | Team: ~USD 29/app/mo | USD 350+/app/yr | Volume-based; consider self-hosted at scale. |
2860
- | Datadog / New Relic / Azure Monitor | APM, logs, traces | Host-based or ingestion-based | USD 1,500 – USD 10,000+/yr | Strong for microservices; may overlap with Prometheus/Grafana. |
2861
- | SonarCloud / SonarQube | Static analysis, quality gate | Developer: ~USD 25/dev/mo; Enterprise billed annually | USD 2,000 – USD 15,000/yr | SonarCloud is SaaS; SonarQube can be self-hosted with infra cost. |
2862
- | Snyk / Trivy / Dependabot | Dependency & container vulnerability scanning | Snyk Team: ~USD 19/dev/mo; Trivy is free (OSS) | USD 0 – USD 5,000/yr | Trivy + GitHub Dependabot cover most needs at no extra cost. |
2863
- | Vercel / Netlify / Azure Static Web Apps | Frontend preview deploys and hosting | Pro: ~USD 20/user/mo | USD 2,400 – USD 8,000/yr | GHCR + K8s can replace this; preview environments add cost. |
2864
- | Azure Container Registry (ACR) or AWS ECR | Container image registry | Basic: USD 0.167/day + storage/egress | USD 100 – USD 1,000/yr | Often cheaper than GHCR at high throughput; pay for storage + egress. |
2865
- | Auth0 / WorkOS / Clerk | Managed auth (if not custom) | Free to USD 23+/mo | USD 0 – USD 2,000/yr | Custom NestJS auth saves license cost but needs more engineering time. |
2866
- | Storybook | Component library documentation + review | OSS free; Cloud from USD 20/mo | USD 0 – USD 5,000/yr | Includes collaboration add-ons in paid tiers. |
2968
+ | Tool / Service | Purpose | Typical Tier | Est. Cost | Notes |
2969
+ | ----------------------------------------- | ------------------------------------------------- | ----------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------- |
2970
+ | GitHub Enterprise / Teams | Repo hosting, Actions minutes, GHCR, Environments | Team: USD 4/user/mo; Enterprise: USD 21/user/mo | USD 2,400 – USD 25,000/yr | Public repos have free Actions minutes; private minutes are billed beyond the free tier (2,000 min/mo). |
2971
+ | Sentry (Error Tracking) | Frontend & backend error monitoring | Team: ~USD 29/app/mo | USD 350+/app/yr | Volume-based; consider self-hosted at scale. |
2972
+ | Datadog / New Relic / Azure Monitor | APM, logs, traces | Host-based or ingestion-based | USD 1,500 – USD 10,000+/yr | Strong for microservices; may overlap with Prometheus/Grafana. |
2973
+ | SonarCloud / SonarQube | Static analysis, quality gate | Developer: ~USD 25/dev/mo; Enterprise billed annually | USD 2,000 – USD 15,000/yr | SonarCloud is SaaS; SonarQube can be self-hosted with infra cost. |
2974
+ | Snyk / Trivy / Dependabot | Dependency & container vulnerability scanning | Snyk Team: ~USD 19/dev/mo; Trivy is free (OSS) | USD 0 – USD 5,000/yr | Trivy + GitHub Dependabot cover most needs at no extra cost. |
2975
+ | Vercel / Netlify / Azure Static Web Apps | Frontend preview deploys and hosting | Pro: ~USD 20/user/mo | USD 2,400 – USD 8,000/yr | GHCR + K8s can replace this; preview environments add cost. |
2976
+ | Azure Container Registry (ACR) or AWS ECR | Container image registry | Basic: USD 0.167/day + storage/egress | USD 100 – USD 1,000/yr | Often cheaper than GHCR at high throughput; pay for storage + egress. |
2977
+ | Auth0 / WorkOS / Clerk | Managed auth (if not custom) | Free to USD 23+/mo | USD 0 – USD 2,000/yr | Custom NestJS auth saves license cost but needs more engineering time. |
2978
+ | Storybook | Component library documentation + review | OSS free; Cloud from USD 20/mo | USD 0 – USD 5,000/yr | Includes collaboration add-ons in paid tiers. |
2867
2979
 
2868
2980
  ### C.3 Infrastructure Costs (Monthly)
2869
2981
 
2870
- | Environment | Typical Specs | Est. Monthly | Annual Est. |
2871
- |---|---|---|---|
2872
- | Dev (1 replica, no HA) | 1 vCPU / 2 GB RAM | USD 40 – USD 120 | USD 500 – USD 1,500 |
2873
- | Staging (2 replicas) | 2 vCPU / 4 GB RAM each | USD 120 – USD 300 | USD 1,500 – USD 3,600 |
2874
- | Production (min 3 replicas, autoscale) | 2 vCPU / 4 GB RAM each + load balancer | USD 300 – USD 900 | USD 3,600 – USD 10,800 |
2875
- | Managed PostgreSQL (e.g., Azure Database, RDS, Cloud SQL) | 2 vCPU / 8 GB + storage | USD 150 – USD 500 | USD 1,800 – USD 6,000 |
2876
- | Managed Redis / Valkey | 1 vCPU / 2 GB | USD 15 – USD 50 | USD 180 – USD 600 |
2877
- | Object Storage (S3 / Blob) | 1 TB + egress | USD 20 – USD 80 | USD 240 – USD 960 |
2878
- | SSL (managed by ingress or ACM) | -- | Often free | USD 0 |
2879
- | DNS / domain | -- | USD 10 – USD 30 | USD 120 – USD 360 |
2982
+ | Environment | Typical Specs | Est. Monthly | Annual Est. |
2983
+ | --------------------------------------------------------- | -------------------------------------- | ----------------- | ---------------------- |
2984
+ | Dev (1 replica, no HA) | 1 vCPU / 2 GB RAM | USD 40 – USD 120 | USD 500 – USD 1,500 |
2985
+ | Staging (2 replicas) | 2 vCPU / 4 GB RAM each | USD 120 – USD 300 | USD 1,500 – USD 3,600 |
2986
+ | Production (min 3 replicas, autoscale) | 2 vCPU / 4 GB RAM each + load balancer | USD 300 – USD 900 | USD 3,600 – USD 10,800 |
2987
+ | Managed PostgreSQL (e.g., Azure Database, RDS, Cloud SQL) | 2 vCPU / 8 GB + storage | USD 150 – USD 500 | USD 1,800 – USD 6,000 |
2988
+ | Managed Redis / Valkey | 1 vCPU / 2 GB | USD 15 – USD 50 | USD 180 – USD 600 |
2989
+ | Object Storage (S3 / Blob) | 1 TB + egress | USD 20 – USD 80 | USD 240 – USD 960 |
2990
+ | SSL (managed by ingress or ACM) | -- | Often free | USD 0 |
2991
+ | DNS / domain | -- | USD 10 – USD 30 | USD 120 – USD 360 |
2880
2992
 
2881
2993
  > These are mid-2026 market reference prices. Cloud providers frequently discount reserved/commitment terms.
2882
2994
 
@@ -2889,28 +3001,28 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
2889
3001
 
2890
3002
  ### C.5 Open-Source vs Commercial Alternatives (2026)
2891
3003
 
2892
- | Need | Commercial Option | OSS Alternative | Est. Savings | Trade-offs / Notes |
2893
- |---|---|---|---|---|
2894
- | CI/CD | GitHub Actions (private) | **Woodpecker**, **Gitea Actions**, Jenkins, GitLab CI (self-hosted runners) | Up to ~USD 10,000/yr on high volume | Self-hosted runners need infra + maintenance. Woodpecker/Gitea are easiest to adopt if you want to stay off cloud minutes. |
2895
- | Error Tracking | Sentry Team | **Sentry Self-Hosted**, GlitchTip | ~USD 350 – USD 3,000+/yr saved | Self-hosted Sentry needs ~1–2 vCPU + 2–4 GB RAM per app. GlitchTip is lighter. |
2896
- | Monitoring/APM | Datadog / New Relic | **Prometheus + Grafana**, SigNoz, Uptime Kuma | ~USD 1,500 – USD 10,000+/yr saved | OSS has higher setup cost. Best for teams with in-house SRE knowledge. |
2897
- | Static Analysis | SonarCloud | **SonarQube Community**, ESLint + TypeScript strict + Coverage, CodeQL | ~USD 2,000 – USD 15,000/yr saved | SonarQube CE is capable. GitHub CodeQL is free for public and private repos. |
2898
- | Dependency Scanning | Snyk | **Trivy**, OWASP Dependency-Check, GitHub Dependabot, Renovate | ~USD 0 – USD 5,000/yr saved | Dependabot + Trivy cover the majority of needs at zero license cost. |
2899
- | Auth (if not custom) | Auth0 / Clerk | **Keycloak**, Authentik, SuperTokens | ~USD 0 – USD 2,000/yr saved | Keycloak is the most mature but heavier. SuperTokens is lighter and developer-friendly. |
2900
- | Component Review | Storybook Cloud | **Storybook OSS**, Chromatic (paid) | ~USD 0 – USD 5,000/yr saved | Storybook OSS is fully free. Chromatic adds hosted review at ~USD 20/app/mo. |
2901
- | Frontend Hosting | Vercel / Netlify / SWA | **Nginx + Cloudflare**, Caddy, self-hosted K8s + GHCR | ~USD 2,400 – USD 8,000/yr saved | GHCR + K8s is usually cheaper at scale; preview environments cost more effort. |
2902
- | Container Registry | ACR / ECR | **GHCR**, Harbor (self-hosted) | ~USD 100 – USD 1,000/yr saved | GHCR is free for public/private with GitHub Actions. Harbor is good for air-gapped/on-prem. |
3004
+ | Need | Commercial Option | OSS Alternative | Est. Savings | Trade-offs / Notes |
3005
+ | -------------------- | ------------------------ | --------------------------------------------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
3006
+ | CI/CD | GitHub Actions (private) | **Woodpecker**, **Gitea Actions**, Jenkins, GitLab CI (self-hosted runners) | Up to ~USD 10,000/yr on high volume | Self-hosted runners need infra + maintenance. Woodpecker/Gitea are easiest to adopt if you want to stay off cloud minutes. |
3007
+ | Error Tracking | Sentry Team | **Sentry Self-Hosted**, GlitchTip | ~USD 350 – USD 3,000+/yr saved | Self-hosted Sentry needs ~1–2 vCPU + 2–4 GB RAM per app. GlitchTip is lighter. |
3008
+ | Monitoring/APM | Datadog / New Relic | **Prometheus + Grafana**, SigNoz, Uptime Kuma | ~USD 1,500 – USD 10,000+/yr saved | OSS has higher setup cost. Best for teams with in-house SRE knowledge. |
3009
+ | Static Analysis | SonarCloud | **SonarQube Community**, ESLint + TypeScript strict + Coverage, CodeQL | ~USD 2,000 – USD 15,000/yr saved | SonarQube CE is capable. GitHub CodeQL is free for public and private repos. |
3010
+ | Dependency Scanning | Snyk | **Trivy**, OWASP Dependency-Check, GitHub Dependabot, Renovate | ~USD 0 – USD 5,000/yr saved | Dependabot + Trivy cover the majority of needs at zero license cost. |
3011
+ | Auth (if not custom) | Auth0 / Clerk | **Keycloak**, Authentik, SuperTokens | ~USD 0 – USD 2,000/yr saved | Keycloak is the most mature but heavier. SuperTokens is lighter and developer-friendly. |
3012
+ | Component Review | Storybook Cloud | **Storybook OSS**, Chromatic (paid) | ~USD 0 – USD 5,000/yr saved | Storybook OSS is fully free. Chromatic adds hosted review at ~USD 20/app/mo. |
3013
+ | Frontend Hosting | Vercel / Netlify / SWA | **Nginx + Cloudflare**, Caddy, self-hosted K8s + GHCR | ~USD 2,400 – USD 8,000/yr saved | GHCR + K8s is usually cheaper at scale; preview environments cost more effort. |
3014
+ | Container Registry | ACR / ECR | **GHCR**, Harbor (self-hosted) | ~USD 100 – USD 1,000/yr saved | GHCR is free for public/private with GitHub Actions. Harbor is good for air-gapped/on-prem. |
2903
3015
 
2904
3016
  ### C.6 OSS Cost-Scoring Cheat Sheet
2905
3017
 
2906
- | Solution | Setup Effort | Maintenance | When It Wins | When to Buy Commercial |
2907
- |---|---|---|---|---|
2908
- | Prometheus + Grafana | Medium | Medium | Metrics-heavy infra, in-house SRE skill | When you want hosted dashboards, alerting, and support SLAs |
2909
- | Sentry Self-Hosted | Medium | Medium | Apps with many errors and sensitive logs | When uptime and incident response are more important than license cost |
2910
- | Keycloak | High | High | Enterprise SSO, internal identity, B2B | When identity is not your core competency |
2911
- | Woodpecker / Gitea | Medium | Medium | Heavy CI usage, private repo cost control | When you want zero per-minute billing and full data control |
2912
- | Jenkins | High | High | Legacy enterprise estates, Windows workloads | When your team already knows Jenkins and plugin ecosystem |
2913
- | Harbor | Medium | Medium | Air-gapped/on-prem registries, large images | When egress or compliance prevents cloud registries |
3018
+ | Solution | Setup Effort | Maintenance | When It Wins | When to Buy Commercial |
3019
+ | -------------------- | ------------ | ----------- | -------------------------------------------- | ---------------------------------------------------------------------- |
3020
+ | Prometheus + Grafana | Medium | Medium | Metrics-heavy infra, in-house SRE skill | When you want hosted dashboards, alerting, and support SLAs |
3021
+ | Sentry Self-Hosted | Medium | Medium | Apps with many errors and sensitive logs | When uptime and incident response are more important than license cost |
3022
+ | Keycloak | High | High | Enterprise SSO, internal identity, B2B | When identity is not your core competency |
3023
+ | Woodpecker / Gitea | Medium | Medium | Heavy CI usage, private repo cost control | When you want zero per-minute billing and full data control |
3024
+ | Jenkins | High | High | Legacy enterprise estates, Windows workloads | When your team already knows Jenkins and plugin ecosystem |
3025
+ | Harbor | Medium | Medium | Air-gapped/on-prem registries, large images | When egress or compliance prevents cloud registries |
2914
3026
 
2915
3027
  ### C.7 Backend-Oriented Cost Notes (Frontend Impact)
2916
3028
 
@@ -2938,5 +3050,5 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
2938
3050
  - Azure Hybrid Benefit: https://azure.microsoft.com/en-us/pricing/benefits/azure-hybrid-benefit/
2939
3051
  - Visual Studio subscriptions: https://visualstudio.microsoft.com/subscriptions/
2940
3052
  - FastTrack: https://azure.microsoft.com/en-us/programs/azure-fasttrack/
2941
- Actual entitlement depends on partner tier, agreement, and region; treat these as **items to explore** with your account team.
3053
+ Actual entitlement depends on partner tier, agreement, and region; treat these as **items to explore** with your account team.
2942
3054
  - Cloud infrastructure ranges are derived from standard managed VM, DB, and storage pricing pages; costs can be lower with reserved instances, savings plans, or partner credits.