# Architecture, Code Quality & Standards — GB4.7MFE

Last analyzed: 2026-02-23

---

## Project Architecture
- **Pattern:** Angular 20 MFE monorepo with Native Federation
- **Host:** `projects/gbhost/` — shell, routing, layout
- **Remote modules:** `projects/[module]/` — 39 independently deployable apps
- **Shared features:** `features/[name]/` — 67 shared feature components
- **Shared libraries:** `libs/common/`, `libs/gbdirectives/`, `libs/gbpipes/`
- **Module anatomy:** each project has `master/`, `transaction/`, `others/`, `service/`, `dbservice/`
- **Layer flow:** Component → Service → DBService → GbHttpService → Backend

## State Management — DECIDED: Full Signals Migration (2026-02-23)
- **Migrate fully to Angular Signals. Drop NGXS. Eliminate all BehaviorSubjects in components.**
- NGXS is trivially unused: only 2 `store.dispatch()` calls total, 0 `selectSnapshot`, stores only LoginDTO + FilterCriteria
  - Replace with `GbAppStateService` signal service in `libs/common/`
  - 2 files to update: `features/gbfilter/gbfilter.component.ts:1492`, `libs/common/src/lib/gbservice/gbhttpservice/gbhttp.service.ts:453`
- DataPassingService already mostly signals; convert 3 remaining BehaviorSubjects (reportIdentifiersSource, columnWidths, keep triggerNextSetEvent as Subject)
- 2,483 `.subscribe()` calls in components → replace with `toSignal()` (no leaks, no cleanup)
- `toSignal()` currently at 0 usages — needs adoption
- **RxJS stays in services (HttpClient, streams). `toSignal()` is the component boundary.**

### Signal Patterns — Use in ALL New Code
```typescript
// HTTP data in component (replaces subscribe + takeUntil)
data = toSignal(this.service.getData(), { initialValue: [] as MyType[] });

// HTTP with loading/error state (Angular 20)
resource = rxResource({
  request: () => ({ id: this.id() }),
  loader: ({ request }) => this.service.get(request.id)
});
// resource.value(), resource.isLoading(), resource.error() — all free

// Derived state (replaces subscribe-to-derive pattern)
filtered = computed(() => this.data().filter(x => x.active));

// Read DataPassingService state directly — no subscribe
visible = this.dp.isMenuVisible; // already a signal
```

### Migration Sequence
1. Drop NGXS → GbAppStateService (2 files) — do first
2. DataPassingService remaining BehaviorSubjects → signals
3. Apply toSignal() to gblayout, gbheader, gbusersetting (known leak locations)
4. ALL new modules: toSignal()/rxResource() from day one
5. Existing modules: convert subscribes opportunistically when touching files

## Form System
- `GBBaseFormGroup` in `libs/common/src/lib/gbformgroup/gbformgroup.ts`
- Reads form JSON from `projects/gbhost/public/formjson/[formname].json` (440+ files)
- No TypeScript interface for form JSON — no compile-time validation
- **Rule for new forms:** Define `FormJsonSchema` interface; validate JSON against it at build time

---

## Known Technical Debt

### Monolithic HTTP Service
- `libs/common/src/lib/gbservice/gbhttpservice/` — 1499 lines
- Handles: HTTP calls, encryption, duplicate request tracking, environment detection, versioning
- Single point of failure for entire app
- **Plan to split:**
  - `GbHttpCoreService` — actual HTTP calls
  - `GbEncryptionService` — AES encryption/decryption
  - `GbRequestDeduplicationService` — duplicate tracking
  - `GbEnvironmentService` — environment/config detection

### Excessive `any` Typing
- Signals: `signal<any[]>([])` throughout — defeats TypeScript strict mode
- Services: `Observable<any>` in all DB services
- **Rule:** Define interfaces for all API responses. `any` should cause PR review failure.

### Missing Tooling (not yet added)
- No ESLint config — add `ng add @angular-eslint/schematics`
- No Prettier config — add with `prettier` + `.prettierrc`
- No pre-commit hooks — add Husky + lint-staged
- No bundle size budgets in `angular.json`
- No E2E tests (Cypress or Playwright)
- Unit test coverage unknown — both Karma and Jest configured, neither actively used

### Magic Strings
- Form names, menu IDs, module names passed as string literals
- `@Inject('selectedId')` — string injection tokens
- `new GBBaseFormGroup(injector, 'branch', MenuId, 'admin')` — no type safety
- **Fix:** Use `InjectionToken<T>`, enums for module names, constants for form IDs

### Dual i18n Libraries
- Both `@jsverse/transloco` and `@ngx-translate/core` bundled — remove ngx-translate

---

## Mandatory Patterns for All New Code

### Component Template (New Standard — Signals-first)
```typescript
@Component({
  selector: 'gb-[name]',
  imports: [...],
  templateUrl: './[name].component.html',
  styleUrl: './[name].component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,  // ALWAYS
})
export class [Name]Component {
  // Services
  private service = inject(MyService);

  // HTTP data — no subscribe, no ngOnDestroy needed
  data = toSignal(this.service.getData(), { initialValue: [] as MyType[] });

  // Derived state
  filtered = computed(() => this.data().filter(x => x.active));

  // Local UI state
  isOpen = signal(false);
}
// destroy$ and ngOnDestroy only needed if using timers or raw DOM event listeners
```

### Service Injection
```typescript
// Use inject() — not constructor injection
private service = inject(MyService);
// Do NOT inject ChangeDetectorRef in new components — signals handle CD automatically
```

### HTTP Subscriptions (Legacy pattern — do not use in new code)
```typescript
// OLD (still exists in 2483 places — migrate opportunistically):
this.service.getData()
  .pipe(takeUntil(this.destroy$))
  .subscribe({ next: (data) => {...}, error: (err) => {...} });

// NEW:
data = toSignal(this.service.getData(), { initialValue: [] as MyType[] });
```

### State (Signals)
```typescript
// Local state
count = signal(0);
// Derived (lazy, cached, auto-tracked)
doubled = computed(() => this.count() * 2);
// Effects — only for side effects, NOT for deriving state
effect(() => { /* side effect only */ });
```

### No `any`
```typescript
// BAD
data = signal<any[]>([]);
// GOOD
data = signal<Applicant[]>([]);
```

---

## New Module Checklist
When creating a new module/subproject, verify:
- [ ] `ChangeDetectionStrategy.OnPush` on every component
- [ ] HTTP data loaded via `toSignal()` or `rxResource()` — no raw `.subscribe()` in components
- [ ] `destroy$` + ngOnDestroy only if using setInterval/setTimeout/raw DOM listeners
- [ ] All signals typed with real interfaces (no `any`)
- [ ] No NGXS — use `GbAppStateService` or DataPassingService signals
- [ ] No BehaviorSubjects in components or shared services
- [ ] All display strings use Transloco translation keys
- [ ] No hardcoded credentials or keys
- [ ] No `bypassSecurityTrust*` — use DOMPurify for HTML content
- [ ] Dialog widths use `min(Xpx, 95vw)`
- [ ] RTL styles added for layout components
- [ ] Unit tests for service layer
- [ ] No `console.log` — use GbConsoleService
- [ ] `npm audit` passes for any new dependencies
