# GbLayout Deep Analysis
**Date:** 2026-02-24
**Scope:** `features/gblayout/` — all components, services, templates, styles
**Role:** Main presentation organizer — shell, header, footer, form/report action bars

---

## 1. Component Inventory

| Component | File | Lines | OnPush | Signals |
|---|---|---|---|---|
| GbLayoutComponent | gblayout.component.ts | 88 | ❌ Missing | Partial |
| GbHeaderComponent | gbheader/gbheader.component.ts | 183 | ✅ | Partial |
| GbUserSettingComponent | gbheader/gbusersetting/gbusersetting.component.ts | 336 | ❌ Missing | ❌ None |
| GbNotificationComponent | gbheader/gbnotification/gbnotification.component.ts | 185 | ❌ Missing | ❌ None |
| GbModuleComponent | gbheader/gbmodule/gbmodule.component.ts | 389 | ❌ Missing | ❌ None |
| GbClientComponent | gbheader/gbclient/gbclient.component.ts | 131 | ❌ Missing | ❌ None |
| GbFormActionComponent | gbformaction/formactionbar/gbformaction.component.ts | 1170+ | ❌ Missing | Partial |
| GbReportActionComponent | gbreportaction/reportactionscreen/gbreportaction.component.ts | 500+ | ✅ | Partial |

---

## 2. Critical Security Issues (P0 — Fix Before Any Release)

### SEC-01 — Hardcoded AES-256 Key
**File:** [gbmodule.component.ts:61](features/gblayout/gbheader/gbmodule/gbmodule.component.ts#L61)
**Severity:** P0 — Critical
```typescript
// CURRENT — key baked into source code and in git history
const encryptedModuleId = this.encryptUsingAES256(
  moduleid.ModuleId.toString(),
  '12345678901234567890123456789012'   // ← hardcoded key
);
```
**Impact:** Anyone with source code access can decrypt all `moduleid` query parameters. Key also uses ECB mode (no IV), which is cryptographically weak.
**Fix:** Load the key from server-side config (`/fws/Config.svc/GetEncryptionKey`), inject via `InjectionToken<string>`, and switch to CBC/GCM mode. See CLAUDE.md security standards.

---

### SEC-02 — SSO Token in localStorage + console.log
**File:** [gbusersetting.component.ts:111-112](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L111)
**Severity:** P0 — Critical
```typescript
let token = localStorage.getItem('SSOToken');
console.log('SSO Token:', token);   // ← token exposed in browser console
```
**Impact:** Any XSS attack or browser extension can steal the SSO token. The `console.log` sends it to browser dev tools in plaintext.
**Fix:** SSO token must move to `httpOnly` cookies (set server-side). Remove `console.log` for sensitive data.

---

### SEC-03 — bypassSecurityTrustHtml on User-Controlled Content
**File:** [gbnotification.component.ts:51](features/gblayout/gbheader/gbnotification/gbnotification.component.ts#L51)
**Severity:** P0 — Critical
```typescript
sanitizeHtml(message: string): SafeHtml {
  return this.sanitizer.bypassSecurityTrustHtml(message);  // ← no actual sanitization
}
```
**Impact:** Any notification message from the API containing `<script>` or event handler attributes executes in the user's browser. Stored XSS vector.
**Fix:** Use DOMPurify before passing to Angular — `DOMPurify.sanitize(message)` — then bind via `[innerHTML]` without bypass, or use `this.sanitizer.sanitize(SecurityContext.HTML, message)`.

---

### SEC-04 — Raw console.log Calls with Data
**Files (multiple):**
- [gbusersetting.component.ts:186,198](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L186)
- [gbusersetting.component.ts:281](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L281)

```typescript
console.log('rr', res.responseValue)      // API response data in console
console.log('rr', this.supportValidTill)  // business data exposed
console.error('Error saving data:', err)  // use GbConsoleService
```
**Fix:** Replace all `console.*` with `GbConsoleService` as per CLAUDE.md standard.

---

### SEC-05 — Jasper Credentials in ReportAction (referenced in gblayout)
**File:** [gbreportaction.component.ts](features/gblayout/gbreportaction/reportactionscreen/gbreportaction.component.ts) (see line ~408 per prior analysis)
**Note:** Confirmed present — hardcoded `j_username=jasperadmin&j_password=jasperadmin` in PDF export URL.

---

## 3. Memory Leaks

### LEAK-01 — window.addEventListener('resize') Never Removed
**File:** [gblayout.component.ts:60](features/gblayout/gblayout.component.ts#L60)
```typescript
ngAfterViewInit() {
  this.adjustMenuTreeHeight();
  window.addEventListener('resize', () => this.adjustMenuTreeHeight());  // ← never removed
  // ...
}
```
**Impact:** Each navigation to a route that creates GbLayout adds a new listener. After 10 navigations = 10 listeners running on every resize event. Causes CPU spikes and holds references preventing GC.
**Fix:** Use `HostListener('window:resize')` (already present above at :44) — the second manual listener at :60 is entirely redundant and should be deleted.

---

### LEAK-02 — Unclean Subscription in ngOnInit
**File:** [gblayout.component.ts:53](features/gblayout/gblayout.component.ts#L53)
```typescript
ngOnInit(): void {
  this.themeService.fetchUserThemePreferences();
  this.breakpointService.getCurrentBreakpoint().subscribe((result: any) => {  // ← no takeUntil
    this.sharedservice.currentDevice.set(result.breakpoint);
  });
}
```
**Impact:** Subscription lives for entire app lifetime. Since GbLayout is a singleton shell, this is less critical than in transient components but still incorrect — it prevents future refactoring where the layout is replaced.
**Fix:** Convert to `toSignal()` — one line, no cleanup needed:
```typescript
currentDevice = toSignal(this.breakpointService.getCurrentBreakpoint());
effect(() => this.sharedservice.currentDevice.set(this.currentDevice()?.breakpoint));
```

---

### LEAK-03 — Unclean Subscription in UserSetting
**File:** [gbusersetting.component.ts:93](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L93)
```typescript
this.service.getportletservice().subscribe((Portletdata: any) => {  // ← no cleanup
  let PortletList = Portletdata?.responseValue;
  this.MySettings = PortletList?.find((data: any) => data.PageId == -13999777961);
});
```
**Impact:** Each time the user setting menu opens (component re-created if used inside a menu), this subscription can accumulate.
**Fix:** `this.service.getportletservice().pipe(take(1)).subscribe(...)` since it's a one-time fetch.

---

### LEAK-04 — 1-Second Polling Interval in GbClientComponent
**File:** [gbclient.component.ts:39](features/gblayout/gbheader/gbclient/gbclient.component.ts#L39)
```typescript
this.storageSubscription = interval(1000).subscribe(() => {
  const current = sessionStorage.getItem('LoginDTO');
  if (current !== this.lastLoginDTOString) {
    this.loadLoginDTO();
  }
});
```
**Impact:** This fires 3,600 times per hour, every hour the app is open. Each tick reads from sessionStorage and does a string comparison. While the subscription is cleaned up in `ngOnDestroy` (✅ correctly done), the polling frequency is excessive.
**Note:** The `window.addEventListener('storage')` at line 36 already handles cross-tab changes. The interval is only for same-tab changes. A better approach is to use Angular signals or a shared service observable triggered on explicit login DTO updates instead of polling.

---

## 4. Performance Issues

### PERF-01 — Missing OnPush Change Detection on Critical Components
**Affected:**
- `GbLayoutComponent` — no `changeDetection: ChangeDetectionStrategy.OnPush`
- `GbUserSettingComponent` — no `changeDetection`
- `GbNotificationComponent` — no `changeDetection`
- `GbModuleComponent` — no `changeDetection`
- `GbClientComponent` — no `changeDetection`
- `GbFormActionComponent` — no `changeDetection` (50+ inputs, huge re-render cost)

**Impact:** Without OnPush, Angular's default CD runs for every browser event (click, mouse move, keyup) across the entire app and re-checks every binding in these components. For GbLayout (the shell), this means the layout re-evaluates bindings on every DOM event globally.
**Fix:** Add `changeDetection: ChangeDetectionStrategy.OnPush` to all components. With signals, this is mostly automatic — CD triggers only when signal values change.

---

### PERF-02 — ChangeDetectorRef.detectChanges() in an effect()
**File:** [gblayout.component.ts:36-40](features/gblayout/gblayout.component.ts#L36)
```typescript
effect(() => {
  const isChecklistTourVisible = this.checklistTourService.isChecklistTourVisible();
  this.isLightboxVisible = isChecklistTourVisible;
  this.cdr.detectChanges();   // ← redundant with signals
});
```
**Impact:** Manually calling `detectChanges()` inside an `effect()` forces synchronous CD on top of Angular's own scheduling. This double-triggers CD and defeats the benefit of signals.
**Fix:** Convert `isLightboxVisible` to a computed signal:
```typescript
isLightboxVisible = this.checklistTourService.isChecklistTourVisible;
// template: *ngIf="isLightboxVisible()"
```

---

### PERF-03 — ChangeDetectorRef.detectChanges() in GbHeaderComponent effect()
**File:** [gbheader.component.ts:57-66](features/gblayout/gbheader/gbheader.component.ts#L57)
```typescript
effect(() => {
  this.isGbAi = this.sharedservice.isAiVisible();
  // ...
  this.cdr.detectChanges();   // ← same anti-pattern
});
```
**Same issue as PERF-02.** Use `computed()` or direct signal binding in template.

---

### PERF-04 — getFilteredModules() Called on Every CD Cycle
**File:** [gbmodule.component.ts:267](features/gblayout/gbheader/gbmodule/gbmodule.component.ts#L267)
```typescript
getFilteredModules() {
  if (!this.searchQuery.trim()) return this.ModuleList;
  const query = this.searchQuery.toLowerCase();
  return this.ModuleList.filter(/* ... */);
}
```
The template calls `getFilteredModules()` in an `*ngFor`. Every CD cycle (every keypress, click, event) re-executes this filter over the full module list.
**Fix:** With OnPush + signals: convert `searchQuery` to a `signal<string>('')` and use `computed()`:
```typescript
filteredModules = computed(() => {
  const q = this.searchQuery().toLowerCase().trim();
  return q ? this.ModuleList().filter(m => m.ModuleName.toLowerCase().includes(q)) : this.ModuleList();
});
```

---

### PERF-05 — DOM Queries in Event Handlers (gbmodule, gbheader)
**File:** [gbmodule.component.ts:139,175,247](features/gblayout/gbheader/gbmodule/gbmodule.component.ts#L139)
```typescript
const viewButtons = Array.from(document.querySelectorAll('.view-btn')) as HTMLElement[];
const elements = Array.from(document.querySelectorAll(selector)) as HTMLElement[];
```
These `querySelectorAll` calls run on every keydown event. They traverse the entire DOM and allocate new arrays.
**Fix:** Cache element references with `@ViewChildren` and `QueryList`, or use a `signal<HTMLElement[]>` initialized once after view init.

---

### PERF-06 — Image Preloading Stores HTMLImageElement References
**File:** [gbmodule.component.ts:47](features/gblayout/gbheader/gbmodule/gbmodule.component.ts#L47)
```typescript
preloadedImages: { [key: string]: HTMLImageElement } = {};
preloadImages() {
  this.ModuleList.forEach((module: any) => {
    const img = new Image();
    img.src = imageUrl;
    this.preloadedImages[imageUrl] = img;  // ← stored but never read
  });
}
```
The preloaded images dictionary is populated but never accessed (the template loads images via `src` attributes independently). This stores references to HTMLImageElement objects unnecessarily.
**Fix:** Either use `<link rel="preload">` hints in the document head for true preloading, or remove this dead code entirely since browsers cache images automatically after first load.

---

### PERF-07 — setTimeout Chains for Layout Calculation
**File:** [gblayout.component.ts:61-63](features/gblayout/gblayout.component.ts#L61)
```typescript
ngAfterViewInit() {
  this.adjustMenuTreeHeight();
  window.addEventListener('resize', () => this.adjustMenuTreeHeight());
  setTimeout(() => {
    this.onResize();   // delayed 1 second to "let things load"
  }, 1000);
}
```
The 1-second delay is a workaround for a timing issue. It runs `onResize()` a full second after view init just to set a CSS custom property. This causes a layout flash.
**Fix:** Use `ResizeObserver` on the header element, which fires immediately when the element's size is known:
```typescript
private ro = new ResizeObserver(() => this.adjustMenuTreeHeight());
ngAfterViewInit() { this.ro.observe(this.gbheader.nativeElement); }
ngOnDestroy() { this.ro.disconnect(); }
```

---

### PERF-08 — Dynamic Style Injection via createElement in adjustMenuWidth
**File:** [gbusersetting.component.ts:311-333](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L311)
```typescript
adjustMenuWidth() {
  setTimeout(() => {
    const style = document.createElement('style');
    style.id = 'custom-menu-width';
    // remove old, append new
    document.head.appendChild(style);
  }, 0);
}
```
Injecting `<style>` elements into `<head>` on every menu open causes browser style recalculation. This also bypasses Angular's encapsulation.
**Fix:** Use `::ng-deep` or Angular Material theming tokens for panel width. Or set width via `panelClass` and a static CSS rule.

---

## 5. Code Quality & Maintainability Issues

### QUAL-01 — GbLayoutComponent is Not Using OnPush and Has Mixed Patterns
**File:** [gblayout.component.ts](features/gblayout/gblayout.component.ts)

The component mixes:
- `@HostListener('window:resize')` at line 42 (correct, auto-cleaned)
- `window.addEventListener('resize', ...)` at line 60 (manual, leaking)
- `effect()` with `cdr.detectChanges()` (signals + manual CD = inconsistent)
- `constructor` injection AND `inject()` AND `@ViewChild` — three patterns in one file

**Pattern inconsistencies:**
- `inject(DataPassingService)` (line 27) — modern
- `constructor(private renderer: Renderer2, ...)` (line 34) — legacy
- No `ChangeDetectionStrategy` declared — defaults to Default

---

### QUAL-02 — GbUserSettingComponent Has Dead Code and Inconsistent Patterns
**File:** [gbusersetting.component.ts](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts)

Issues found:
1. **Lines 207-232:** Large block of commented-out code (old DOM manipulation approach) never cleaned up
2. **`adjustTriggerWidth()` (line 99):** Reads `currentWidth` but never uses it — dead code
3. **`adjustMenuWidth()` (line 311):** Never called anywhere in the component — orphaned method
4. **`Sync()` and `Search()` methods (lines 259-291):** Appear to be debugging/dev utilities not tied to any UI — should be removed or moved to a dev/test service
5. **`@ViewChild('tracelightbox')` (line 64):** Referenced but `tracelightbox` doesn't appear to exist in the template
6. **No `ChangeDetectionStrategy.OnPush`** despite being a header component that renders on every app event
7. **`MySettingform!: any`** and many `!: any` typed fields — violates no-`any` rule

---

### QUAL-03 — GbNotificationComponent Missing OnPush and Uses any Types
**File:** [gbnotification.component.ts](features/gblayout/gbheader/gbnotification/gbnotification.component.ts)

```typescript
notifications: any[] = [];       // ← no interface
notificationCount = 0;
isOpen = false;
isLoading = false;
```
All state is plain class properties, no signals, no OnPush. The `cdr.detectChanges()` is called manually after every API response. Define a `Notification` interface and use signals.

Also: `markAsRead()` fires TWO sequential API calls (mark → then fetch). This is a double round-trip. Use the optimistic update (already done) and skip the re-fetch until the user next opens the panel.

---

### QUAL-04 — GbModuleComponent Has No OnPush, No Interfaces, Global DOM Queries
**File:** [gbmodule.component.ts](features/gblayout/gbheader/gbmodule/gbmodule.component.ts)

```typescript
ModuleList: any = [];            // ← no interface for Module
preloadedImages: { [key: string]: HTMLImageElement } = {};  // never read
```
Uses `document.querySelector` and `document.querySelectorAll` throughout keyboard navigation — these are global DOM queries that will match elements outside this component if multiple instances exist.

The `getModulesPerRow()` method (line 246) reads `clientWidth` from the DOM on every keydown — a synchronous layout read that can cause forced reflow.

---

### QUAL-05 — GbFormActionComponent: 1170 Lines, 50+ @Input, No OnPush
**File:** [gbformaction.component.ts](features/gblayout/gbformaction/formactionbar/gbformaction.component.ts)

This is the most problematic component in the layout:
- **50+ `@Input()` properties** — input interface is unmanageable; callers must know which of 50 flags to set
- **No `ChangeDetectionStrategy.OnPush`** — with 50 inputs, Default CD is extremely costly
- **`Subscription` imported but cleanup pattern unclear** — needs audit
- **`OnChanges` with large `ngOnChanges`** — every parent re-render triggers complex logic

**Recommendation:** Group inputs into a typed config interface:
```typescript
interface FormActionConfig {
  showAdd: boolean;
  showEdit: boolean;
  showDelete: boolean;
  showSave: boolean;
  // ...
}
@Input() config!: FormActionConfig;
```

---

### QUAL-06 — GbClientComponent: 1-Second Polling Anti-Pattern
**File:** [gbclient.component.ts:39](features/gblayout/gbheader/gbclient/gbclient.component.ts#L39)

Polling sessionStorage every second to detect OU/period changes is an anti-pattern. The login service should emit a signal/observable when LoginDTO changes, and GbClientComponent should react to that. The current approach:
- Runs 3,600+ times/hour
- Does JSON string comparison every second
- Reads from sessionStorage synchronously (blocks JS main thread)

---

### QUAL-07 — GbHeaderComponent: Duplicate GbAppLogo in Template
**File:** [gbheader.component.html:46](features/gblayout/gbheader/gbheader.component.html#L46)

```html
<div class="header-content-div leftside">
  <div><gb-hbmenu></gb-hbmenu></div>
  <div><gb-applogo></gb-applogo></div>   <!-- LEFT side logo -->
  ...
</div>
<div class="header-content-div">
  ...
  <div>
    <gb-applogo></gb-applogo>             <!-- RIGHT side logo — DUPLICATE -->
  </div>
</div>
```
`gb-applogo` is rendered twice. If it makes HTTP requests or initializes state, this doubles the cost. Likely a copy-paste error during responsive layout work.

---

### QUAL-08 — GbHeaderComponent: Large Commented-Out Code Block
**File:** [gbheader.component.html:13-30](features/gblayout/gbheader/gbheader.component.html#L13)

Lines 13–30 contain a large commented-out block for tour buttons, Announcement, Micro Lesson, and AI buttons. This should either be restored with feature flags or deleted. Leaving commented code in templates causes confusion.

Similarly in `gbheader.component.ts` lines 69–82, the `ngOnInit` and `fetchFeatureTours` methods are fully commented out.

---

### QUAL-09 — GbLayoutComponent: Missing ViewChild Template Refs Match
**File:** [gblayout.component.ts:24-26](features/gblayout/gblayout.component.ts#L24) vs [gblayout.component.html](features/gblayout/gblayout.component.html)

```typescript
@ViewChild('gbheader') gbheader!: ElementRef;
@ViewChild('gbfooter') gbfooter!: ElementRef;
@ViewChild('main') main!: ElementRef;
```
But the template uses:
```html
<div #header class="header">   <!-- 'header' not 'gbheader' -->
<div #main class="main">       <!-- matches ✅ -->
<div #footer class="footer">   <!-- 'footer' not 'gbfooter' -->
```
`@ViewChild('gbheader')` and `@ViewChild('gbfooter')` will ALWAYS be `undefined`. The `adjustMenuTreeHeight()` method checks `if (this.gbheader && this.gbfooter)` — this guard silently swallows the bug, and the height is never actually set. This is a **functional bug**.

---

## 6. Functional Issues

### FUNC-01 — Layout Height Calculation Never Works (ViewChild Mismatch)
**Files:** [gblayout.component.ts:66-75](features/gblayout/gblayout.component.ts#L66) + [gblayout.component.html](features/gblayout/gblayout.component.html)

As noted in QUAL-09, the `@ViewChild` template reference names don't match. `adjustMenuTreeHeight()` will never execute its body because `this.gbheader` and `this.gbfooter` are always `undefined`. The main content area height is never dynamically adjusted.

Additionally, the SCSS has:
```scss
.header { height: 58px; }
// .footer { height: 38px; } // commented out
```
The hardcoded header height in CSS (58px) and the dynamic calculation both try to solve the same problem inconsistently.

---

### FUNC-02 — --screenheight CSS Variable Uses Hardcoded -160px Offset
**File:** [gblayout.component.ts:46](features/gblayout/gblayout.component.ts#L46)

```typescript
document.documentElement.style.setProperty('--screenheight', `${height - 160}px`);
```
`160px` is a magic number with no documented meaning. If the header height changes (currently 58px), or a banner appears, or footer is re-enabled, this value becomes wrong silently.
**Fix:** Compute the offset by measuring actual header + footer heights, or use `calc(100dvh - var(--header-height) - var(--footer-height))` in CSS.

---

### FUNC-03 — GbHeaderComponent Keyboard Navigation Breaks When Modules Panel is Open
**File:** [gbheader.component.ts:138-175](features/gblayout/gbheader/gbheader.component.ts#L138)

The header's `@HostListener('keydown')` intercepts all keyboard events including `Tab`. It calls `event.preventDefault()` on Tab, which prevents the browser's default focus management. The comment says "Prevent tabbing out of the header" but this traps keyboard users inside the header permanently — there is no documented way to escape to the main content.

This is an **accessibility violation** (WCAG 2.1 SC 2.1.2 — No Keyboard Trap).

---

### FUNC-04 — Notification markAsRead Double Round-Trip
**File:** [gbnotification.component.ts:127-142](features/gblayout/gbheader/gbnotification/gbnotification.component.ts#L127)

```typescript
markAsRead(notification: any): void {
  this.setNotifications(this.notifications.filter(...));  // optimistic remove
  this.notificationService.markNotificationRead(id).subscribe({
    next: () => {
      this.notificationService.postnotificationservice(this.userId).subscribe({  // ← second API call
        next: (res) => this.setNotifications(this.extractData(res)),
      });
    },
  });
}
```
After optimistic removal, the code makes an API call to mark as read, then immediately makes another call to re-fetch all notifications. The re-fetch overwrites the optimistic state with identical data, adding one extra round-trip per "mark as read" action. Same issue in `dismiss()`.

---

### FUNC-05 — showWhatsNew Stores Flag in localStorage
**File:** [gbheader.component.ts:124](features/gblayout/gbheader/gbheader.component.ts#L124)

```typescript
showWhatsNew() {
  if (!localStorage.getItem('hideWhatsNew')) {
    this.openWhatsNewDialog();
  }
}
```
The function is called (commented out currently in ngOnInit), but the `openWhatsNewDialog()` never sets `hideWhatsNew` in localStorage after showing. So if this was re-enabled, the dialog would show on every app load, never hiding. Also, `localStorage` is not secure per CLAUDE.md — user preferences should use a server-side user settings API.

---

### FUNC-06 — GbClientComponent Creates GUID and Sets TabId on Every Reload
**File:** [gbclient.component.ts:81-84](features/gblayout/gbheader/gbclient/gbclient.component.ts#L81)

```typescript
const TabId = this.generateGUID();
sessionStorage.setItem('LoginDTO', JSON.stringify(this.loginDTO));
sessionStorage.setItem('TabId', TabId);
```
A new GUID is generated and stored every time `loadLoginDTO()` is called — and `loadLoginDTO()` is called every second by the interval. If `WorkPeriodToDate` is missing (which triggers the API call path), this would generate a new TabId every time the API responds, leading to orphaned Dexie records.

---

### FUNC-07 — getDateFromEpochString Will Crash on Null/Undefined
**File:** [gbusersetting.component.ts:241-248](features/gblayout/gbheader/gbusersetting/gbusersetting.component.ts#L241)

```typescript
getDateFromEpochString(epochString: any): string {
  const epochMilliseconds = Number(epochString.match(/\d+/)[0]);  // ← crash if null
  // ...
}
```
If any date field from the API is `null`, `undefined`, or an unexpected format, `epochString.match(...)` throws `TypeError: Cannot read properties of null`. This will crash the About dialog rendering.
**Fix:** Add null guard: `if (!epochString) return 'N/A';`

---

## 7. i18n Issues

### I18N-01 — Hardcoded English Strings in Templates and TypeScript

The following hardcoded English strings were found:

**GbUserSettingComponent:**
```typescript
this.toastService.info(`See you soon, ${userCode}! Logged out.`, 'Info', 'gb-toast-msg');
```
Must use a Transloco key: `layout.usersetting.logout_message`.

**GbModuleComponent descriptions (lines 329–372):**
```typescript
const descriptions: { [key: string]: string } = {
  'Dashboard': 'Offers real-time visibility into KPIs...',   // ← hardcoded
  'Projects': 'Generate and export comprehensive...',       // ← hardcoded
  // 40+ more entries
};
```
All 40+ module descriptions are hardcoded English strings. These will not translate. They should be Transloco keys, e.g. `modules.description.dashboard`.

**GbClientComponent:**
- Date formatted via `DatePipe` with hardcoded `'en-US'` locale at line 104 — should use Angular's `LOCALE_ID` injection token.

---

### I18N-02 — RTL CSS Missing from Layout Components
**Files:** gblayout.component.scss, gbheader.component.scss, gbmodule.component.scss

Arabic (RTL) is a supported language but no `[dir="rtl"]` CSS selectors exist in any layout SCSS file. The header flex layout, hamburger menu position, and module grid would render incorrectly in RTL mode.

---

## 8. Responsive Design Issues

### RESP-01 — Fixed Pixel Header Height in SCSS
**File:** [gblayout.component.scss:16](features/gblayout/gblayout.component.scss#L16)

```scss
.header { height: 58px; }
```
Hardcoded to 58px. If the header content wraps on smaller screens, content is clipped. Should use `min-content` or a CSS custom property.

---

### RESP-02 — MatDialog Uses Fixed Width
**Files:** gbheader.component.ts (lines 106, 113, 130)

```typescript
this.dialog.open(GbAnnouncementComponent, { width: '600px' });
this.dialog.open(GbMicroLessonComponent, { width: '700px' });
this.dialog.open(GbWhatsNewComponent, { width: '450px' });
```
All three dialogs use fixed widths — will overflow on mobile screens.
**Fix per CLAUDE.md:** `width: 'min(600px, 95vw)', maxWidth: '95vw'`

---

### RESP-03 — Notification Panel Hidden on XSmall Without Accessible Alternative
**File:** [gbheader.component.html:42](features/gblayout/gbheader/gbheader.component.html#L42)

```html
<gb-notification *ngIf="currentDevice !== 'XSmall'"></gb-notification>
```
Notifications are completely hidden on XSmall (mobile) screens with no alternative access point. This is a functional regression for mobile users.

---

## 9. Architecture Recommendations

### ARCH-01 — GbLayoutComponent Should Use Signals Exclusively

**Current state:**
- Mixes constructor injection (Renderer2, BreakpointObserverService) with `inject()` for others
- Uses `effect()` + `cdr.detectChanges()` anti-pattern
- Missing OnPush

**Target state:**
```typescript
@Component({ changeDetection: ChangeDetectionStrategy.OnPush })
export class GbLayoutComponent {
  private breakpointService = inject(BreakpointObserverService);
  sharedservice = inject(DataPassingService);

  isLightboxVisible = this.checklistTourService.isChecklistTourVisible;  // direct signal reference

  private ro = new ResizeObserver(() => this.adjustMenuTreeHeight());
  ngAfterViewInit() { this.ro.observe(this.header.nativeElement); }
  ngOnDestroy() { this.ro.disconnect(); }
}
```

---

### ARCH-02 — GbFormActionComponent Should Be Split

At 1170+ lines and 50+ `@Input()` properties, this component violates the Single Responsibility Principle. Proposed split:

| New Component | Responsibility |
|---|---|
| `GbCrudActionBar` | Save, Delete, Edit, Add New — core CRUD |
| `GbPolicyActionBar` | Policy version save/delete |
| `GbImportExportBar` | Import/Export/Copy/Paste buttons |
| `GbFormSettingsMenu` | Settings dropdown |
| `GbOuPicklist` | BizTransaction + OrgUnit pickers |

Each gets its own config input interface instead of 50 boolean flags.

---

### ARCH-03 — Notification Real-Time Update Strategy

Current: Fetch-on-open only.
Better: Use polling with `rxResource()` or WebSocket when panel is open:

```typescript
notificationResource = rxResource({
  request: () => ({ userId: this.userId, isOpen: this.isOpen() }),
  loader: ({ request }) => request.isOpen
    ? this.notificationService.getNotifications(request.userId)
    : EMPTY
});
```

---

### ARCH-04 — GbModuleComponent: Module Descriptions Should Come from API

40+ hardcoded English module descriptions (lines 329–372) should be returned by the module API or stored in translation files. Hardcoding them means:
- Adding a new module requires code change
- Cannot translate to other languages
- English content differs from actual module naming if module names change

---

### ARCH-05 — GbClientComponent: Replace Polling with Signal-Based Update

LoginDTO changes should be broadcast via a signal in a shared service (e.g., `DataPassingService`). GbClientComponent then reads the signal directly with no polling needed.

---

## 10. Summary — Priority Matrix

| ID | Issue | Severity | Effort |
|---|---|---|---|
| SEC-01 | Hardcoded AES-256 key in gbmodule | P0 Critical | Medium |
| SEC-02 | SSO token in localStorage + console.log | P0 Critical | Medium |
| SEC-03 | bypassSecurityTrustHtml in gbnotification | P0 Critical | Low |
| SEC-04 | console.log with API data | P1 High | Low |
| FUNC-01 | ViewChild names mismatch — height never set | P1 High | Low |
| LEAK-01 | window.addEventListener never removed | P1 High | Low |
| QUAL-07 | Duplicate gb-applogo in header template | P1 High | Low |
| FUNC-07 | getDateFromEpochString crashes on null | P1 High | Low |
| FUNC-03 | Keyboard trap in header (WCAG violation) | P1 High | Medium |
| PERF-01 | Missing OnPush on 6 components | P2 Medium | Medium |
| PERF-02/03 | cdr.detectChanges inside effect() | P2 Medium | Low |
| PERF-04 | getFilteredModules() called every CD | P2 Medium | Low |
| PERF-05 | querySelectorAll on every keydown | P2 Medium | Medium |
| LEAK-02 | Unclean breakpoint subscription | P2 Medium | Low |
| LEAK-03 | Unclean portlet subscription | P2 Medium | Low |
| LEAK-04 | 1-second polling interval | P2 Medium | Medium |
| QUAL-02 | Dead code in UserSetting | P3 Low | Low |
| QUAL-05 | FormAction: 50+ inputs, no interface | P3 Low | High |
| I18N-01 | Hardcoded English strings | P2 Medium | High |
| I18N-02 | No RTL CSS in layout | P2 Medium | Medium |
| RESP-02 | Fixed px dialog widths | P2 Medium | Low |
| FUNC-05 | showWhatsNew never sets hide flag | P3 Low | Low |
| ARCH-02 | FormAction should be split | P3 Low | High |

---

## 11. Quick Wins (can fix in < 1 hour each)

1. **Fix ViewChild template ref names** — rename `#header` → `#gbheader`, `#footer` → `#gbfooter` in [gblayout.component.html](features/gblayout/gblayout.component.html) (FUNC-01)
2. **Remove duplicate window.addEventListener** at [gblayout.component.ts:60](features/gblayout/gblayout.component.ts#L60) — `@HostListener` already handles this (LEAK-01)
3. **Add null guard** to `getDateFromEpochString` (FUNC-07)
4. **Remove duplicate `<gb-applogo>`** from right side of header template (QUAL-07)
5. **Replace bypassSecurityTrustHtml** with `DOMPurify.sanitize()` in gbnotification (SEC-03)
6. **Remove `console.log('SSO Token:', token)`** from logout() (SEC-02)
7. **Fix all MatDialog widths** to `min(Xpx, 95vw)` — 3 dialogs, 3 lines (RESP-02)
8. **Add null guard in getDateFromEpochString** (FUNC-07)
9. **Add `take(1)`** to portlet subscription in gbusersetting.component.ts:93 (LEAK-03)
10. **Delete `adjustMenuWidth()` and `adjustTriggerWidth()`** dead methods from gbusersetting (QUAL-02)
