# gbentityviewer — Deep Analysis

**Date:** 2026-02-24
**Branch:** GBDEV4.7
**Status:** EXPERIMENTAL / NOT PRODUCTION-READY (~30% maturity)

---

## File Listing

```
features/gbentityviewer/
├── gbentityviewer.component.ts              180 lines
├── gbentityviewer.component.html            138 lines
├── gbentityviewer.component.scss            445 lines
├── gbentityviewer.component.spec.ts         empty
├── entity-viewer/
│   ├── entity-viewer.component.ts           105 lines
│   ├── entity-viewer.component.html          69 lines
│   ├── entity-viewer.component.scss         715 lines
│   └── entity-viewer.component.spec.ts      empty
├── field-renderer/
│   ├── field-renderer.component.ts          217 lines
│   ├── field-renderer.component.html         95 lines
│   ├── field-renderer.component.scss        300 lines
│   └── field-renderer.component.spec.ts     empty
├── models/
│   └── layout-schema.model.ts               122 lines
└── services/
    └── entity-layout.service.ts             370 lines

TOTAL: ~2,756 lines | 3 components | 1 service | 1 model | 0 tests
```

---

## Component Hierarchy

```
gbentityviewer.component          ← root shell: entity selection + action bar
  └── entity-viewer.component     ← loads layout schema, renders sections
       └── field-renderer.component (repeated per field)  ← formats + displays single field value
```

---

## P0 Issues — Critical / Production Blockers

### 1. Missing `ChangeDetectionStrategy.OnPush` on all 3 components

| File | Line |
|------|------|
| `gbentityviewer.component.ts` | 8 |
| `entity-viewer.component.ts` | 9 |
| `field-renderer.component.ts` | 8 |

Default change detection runs on every browser event across the app — severe perf degradation given field-renderer renders one instance per field.

**Fix:** Add `changeDetection: ChangeDetectionStrategy.OnPush` to all three `@Component` decorators.

---

### 2. XSS Vulnerability — `[innerHTML]` without DOMPurify

| File | Line |
|------|------|
| `field-renderer.component.html` | 23 |
| `field-renderer.component.ts` | supports `'html'` field type |

`[innerHTML]="value"` renders raw HTML from entity data without sanitization. If any field value originates from user input or an external API, this is an arbitrary JavaScript execution vector.

**Fix:**
```typescript
// safe-html.pipe.ts
import DOMPurify from 'dompurify';

@Pipe({ name: 'safeHtml', pure: true })
export class SafeHtmlPipe implements PipeTransform {
  private sanitizer = inject(DomSanitizer);
  transform(value: string): SafeHtml {
    return this.sanitizer.bypassSecurityTrustHtml(DOMPurify.sanitize(value));
  }
}
```
Replace `[innerHTML]="value"` with `[innerHTML]="value | safeHtml"`.

---

### 3. Unclean Subscriptions — Memory Leaks

| File | Line | Issue |
|------|------|-------|
| `gbentityviewer.component.ts` | 89 | `.subscribe()` — no cleanup |
| `entity-viewer.component.ts` | 47 | `.subscribe()` — no cleanup, no `ngOnDestroy` |

On every `ngOnChanges` call (entity type change), a new subscription is created and the old one never unsubscribed.

**Fix per CLAUDE.md:**
```typescript
// Angular 20 preferred — toSignal replaces subscribe entirely
entityData = toSignal(
  toObservable(this.entityType).pipe(
    switchMap(type => this.layoutService.loadLayout(type))
  ),
  { initialValue: null }
);
```
Or with `rxResource`:
```typescript
layoutResource = rxResource({
  request: () => ({ type: this.entityType() }),
  loader: ({ request }) => this.layoutService.loadLayout(request.type)
});
```

---

### 4. Zero Test Coverage

All three spec files are empty (1 line each). No unit tests for the service either.

**Impact:** No CI validation, no regression protection, breaks if refactored.

---

### 5. Unbounded Cache Growth in `EntityLayoutService`

| File | Lines |
|------|-------|
| `entity-layout.service.ts` | 15–16 |

`layoutCache` and `sampleDataCache` are plain `Map` objects with no eviction policy. For a singleton service in a long-running SPA, this grows indefinitely as users view different entity types.

**Fix:**
```typescript
private readonly CACHE_TTL_MS = 5 * 60 * 1000; // 5 min
private layoutCache = new Map<string, { value: LayoutSchema; expires: number }>();

private getCached(key: string): LayoutSchema | null {
  const entry = this.layoutCache.get(key);
  if (!entry || Date.now() > entry.expires) { this.layoutCache.delete(key); return null; }
  return entry.value;
}
```

---

## P1 Issues — High Priority

### 6. All `any` Types — No Type Safety

| File | Symbols |
|------|---------|
| `gbentityviewer.component.ts:16,30` | `@Input() inputEntityData: any`, `entityData: any` |
| `entity-viewer.component.ts:17` | `@Input() data: any` |
| `field-renderer.component.ts:16,18,20` | `@Input() data: any`, `value: any`, `badgeStyle: any` |
| `field-renderer.component.ts:50,139,161,200,205` | return types all `any` |
| `entity-layout.service.ts:81,88,175` | all method params `any` |
| `layout-schema.model.ts:106` | `value: any` in `Option` interface |

**Fix:** Define `EntityData` interface and `FieldValue` union type in the model file.

---

### 7. Constructor Injection — Violates CLAUDE.md Standards

| File | Line |
|------|------|
| `gbentityviewer.component.ts` | 35 |
| `entity-viewer.component.ts` | 25 |
| `field-renderer.component.ts` | 24 |
| `entity-layout.service.ts` | 22 |

**Fix:** Replace with `private layoutService = inject(EntityLayoutService);`

---

### 8. i18n Violations — Hardcoded English Throughout

**Component-level hardcoded strings (selection):**

| File | Line | String |
|------|------|--------|
| `gbentityviewer.component.ts` | 19–26 | Entity type labels: `"Invoice"`, `"Leave Application"`, etc. |
| `gbentityviewer.component.ts` | 95 | `"Failed to load entity data:"` |
| `gbentityviewer.component.ts` | 122 | `"Entity Details"` |
| `gbentityviewer.component.ts` | 125–144 | Status labels: `"paid"`, `"approved"`, etc. |
| `gbentityviewer.component.ts` | 158,162,164 | `alert()` / `confirm()` English messages |
| `gbentityviewer.component.html` | 5,10,62,92,102,107–115,129–132 | All UI labels |
| `entity-viewer.component.ts` | 57,59,66 | Error/fallback messages |
| `entity-viewer.component.html` | 8,9,68 | Labels |
| `field-renderer.component.html` | 38–39,86 | Table headers, empty state |
| `entity-layout.service.ts` | 109–113,116–122,125,128,138,146,161 | All format output strings |

**Locale hardcoding:**

| File | Line | Issue |
|------|------|-------|
| `gbentityviewer.component.ts` | 126 | `'en-US'` in `formatDate()` |
| `entity-layout.service.ts` | 98,109,116 | `'en-US'`, `'en-IN'` throughout |
| `entity-layout.service.ts` | 161 | US phone format `(XXX) XXX-XXXX` only |

**Fix:** Move all strings to `projects/public/i18n/en.json` under `entityviewer.*` keys and use `translocoService.translate()` / `| transloco` pipe. Use Angular `DatePipe` and `CurrencyPipe` with the active locale.

---

### 9. Stub / Unimplemented Features

| File | Lines | Feature |
|------|-------|---------|
| `gbentityviewer.component.ts` | 156–159 | `shareEntity()` — `alert()` stub |
| `gbentityviewer.component.ts` | 161–166 | `deleteEntity()` — `confirm()` + `alert()` stubs |
| `gbentityviewer.component.ts` | 149 | `getTotalFields()` returns hardcoded `42` |
| `entity-viewer.component.ts` | 28 | `ngOnInit()` commented out |

---

### 10. No RTL CSS Support

All three SCSS files are missing `[dir="rtl"]` selectors. Arabic language is a declared requirement per CLAUDE.md. The custom grid system in `entity-viewer.component.scss` (715 lines) has no RTL awareness at all.

---

### 11. Fixed Heights / Non-Responsive Layout

| File | Line | Issue |
|------|------|-------|
| `gbentityviewer.component.scss` | 76 | `.app-main { height: 550px }` — fixed |
| `field-renderer.component.scss` | — | `min-width: 400px/500px/600px` on tables |

**Fix:** Use `dvh` units or CSS custom properties. Remove fixed min-widths from tables; add horizontal scroll on mobile instead.

---

### 12. `console.error()` Calls — Should Use `GbConsoleService`

| File | Lines |
|------|-------|
| `entity-viewer.component.ts` | 59 |
| `entity-layout.service.ts` | 48, 71 |

---

### 13. Hardcoded Entity Configuration

**`gbentityviewer.component.ts` lines 18–26:** The `allEntities` array with emojis and labels is hardcoded in TypeScript. This ties every new entity type to a code change.

**`entity-layout.service.ts` lines 229–369:** `getVoucherLayout()` is 140 lines of hardcoded JSON-like config inside a TypeScript method. Not scalable.

**Fix:** Move both to JSON config files and load via service. Allows product teams to add entity types without code changes.

---

## P2 Issues — Medium Priority

### 14. Template Method Calls on Every CD Cycle

| File | Lines | Methods |
|------|-------|---------|
| `gbentityviewer.component.html` | 62, 78, 129 | `getEntityTitle()`, `getStatusBadge()`, `getTotalFields()` |
| `field-renderer.component.ts` | 139 | `getInlineStyles()` called multiple times |

**Fix:** Cache as component properties updated only in `ngOnChanges` / signals.

---

### 15. Performance: Expensive Recurring Allocations

| File | Line | Issue |
|------|------|-------|
| `gbentityviewer.component.ts` | 133–144 | Status map object recreated on every `getStatusBadge()` call |
| `entity-layout.service.ts` | 98–103 | `new Intl.NumberFormat()` created on every `formatValue()` call |

**Fix:** Hoist to `readonly` class property or service-level constant.

---

### 16. Accessibility Gaps

- All action buttons (`Print`, `Share`, `Delete`) lack `aria-label`
- `[class.active]` binding lacks corresponding `aria-selected` or `aria-pressed`
- Loading spinner has no `aria-live="polite"` announcement
- Tables missing `scope="col"` on `<th>`, no `aria-label` on `<table>`
- Retry button in `entity-viewer.component.html` has no `aria-label`

---

### 17. Animation Without `prefers-reduced-motion`

| File | Lines |
|------|-------|
| `gbentityviewer.component.scss` | 244 (`animation: spin 1s linear infinite`) |
| `entity-viewer.component.scss` | 81 (`animation: entity-spin 1s`) |

**Fix:**
```scss
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}
```

---

### 18. `customCss` Field in Schema Model — XSS Risk

`layout-schema.model.ts:79` declares `customCss?: string`. If this field is rendered directly into a `<style>` tag or `style` attribute, it's a CSS injection vector.

---

### 19. SCSS Technical Debt

| File | Lines | Issue |
|------|-------|-------|
| `entity-viewer.component.scss` | 46–63 | Generates 100+ utility classes inline — balloons CSS bundle |
| `entity-viewer.component.scss` | — | 100+ `!important` declarations — overriding specificity issues |
| `field-renderer.component.scss` | — | Duplicate table CSS (`array-table` vs `voucher-table-container`) |

---

### 20. Dead Code

| File | Line | Item |
|------|------|------|
| `entity-viewer.component.ts` | 28 | Commented-out `ngOnInit()` |
| `gbentityviewer.component.ts` | 149 | Mock `getTotalFields()` |
| `field-renderer.component.ts` | 173–181 | `getArrayHeaders()` assumes object structure without validation |

---

## Functional Issues & Missing Features

### Critical Functional Bugs

| File | Line | Bug |
|------|------|-----|
| `gbentityviewer.component.ts` | 69 | `filterEntities()` special-cases `'voucher'` with fragile duplicate entity creation |
| `gbentityviewer.component.ts` | 89 | `loadEntity()` called for ALL `ngOnChanges` — including `theme` changes — triggers unnecessary HTTP |
| `entity-viewer.component.ts` | 64–83 | `applyTheme()` mutates the cached schema object directly — corrupts cache for all future uses |
| `entity-layout.service.ts` | 125 | Boolean `true`/`false` rendered as hardcoded English `'Yes'`/`'No'` |
| `entity-layout.service.ts` | 160–163 | Phone formatting only for 10-digit US numbers — silently wrong for other formats |
| `entity-layout.service.ts` | 180 | `getBadgeStyle()` `.toLowerCase()` comparison — breaks for mixed-case status values |

### Missing Features for Claimed General-Purpose Use

| Feature | Status |
|---------|--------|
| Print view | `window.print()` only — no print-specific layout or stylesheet |
| Email view | Not implemented |
| Share functionality | `alert()` stub |
| Delete with confirmation | `confirm()` stub |
| Pagination for large data tables | Not implemented — all rows rendered |
| Custom column ordering | Not implemented — `Object.entries()` order is arbitrary |
| Field-level permissions/visibility | Not in model |
| Lazy load sections | Not implemented |
| Export formats beyond JSON | Not implemented |
| Array data pagination | Not implemented |

---

## Architecture Assessment

### What Works Well
- Clear 3-level component hierarchy (shell → layout → field)
- `EntityLayoutService` correctly encapsulates format/layout logic
- `LayoutSchema` model separates config from rendering
- Service-level caching concept is correct (just needs TTL)

### Architectural Problems

1. **No state management** — each component manages its own loading/error/data state independently with no coordination. When parent changes `entityType`, multiple components reload simultaneously.

2. **Not signals-based** — Uses `ngOnChanges` + raw `subscribe` pattern. Should use `input()` signals + `rxResource` per CLAUDE.md (Angular 20).

3. **Config in code** — `allEntities` in component, `getVoucherLayout()` in service. Zero extensibility without code changes.

4. **No error boundary** — errors in `field-renderer` are uncaught; no fallback rendering.

5. **Print/Email views not architected** — these are fundamentally different render modes that need separate layout/CSS concerns. The current architecture has no mechanism for this.

### Recommended Architecture (Target State)

```
gbentityviewer.component          ← input signals: entityType, data, mode ('view'|'print'|'email')
  ├── entity-viewer.component     ← rxResource-based layout loading, mode-aware rendering
  │   └── field-renderer.component ← pure/signals, SafeHtmlPipe, strongly typed
  ├── entity-print.component      ← print-optimized template + @media print CSS
  └── entity-email.component      ← email-safe HTML output component

EntityLayoutService
  ├── loads layouts from JSON assets (not hardcoded)
  ├── TTL cache
  └── locale-aware formatting via injected LOCALE_ID

layouts/
  ├── voucher-layout.json
  ├── invoice-layout.json
  └── [entity-type]-layout.json

i18n/en.json → entityviewer.*
```

---

## Prioritized Fix Roadmap

### Week 1 — P0 Fixes (Code Health / Security)
- [ ] Add `ChangeDetectionStrategy.OnPush` to all 3 components
- [ ] Fix XSS: replace `[innerHTML]` with `DOMPurify` safe pipe
- [ ] Fix subscriptions: migrate to `toSignal()` / `rxResource()`
- [ ] Fix `applyTheme()` mutation bug — clone schema before mutating
- [ ] Add TTL to service caches
- [ ] Replace all `any` with typed interfaces

### Week 2 — P1 Fixes (Standards / i18n)
- [ ] Replace constructor injection with `inject()`
- [ ] Move all strings to Transloco i18n keys
- [ ] Replace `'en-US'`/`'en-IN'` hardcoding with injected `LOCALE_ID`
- [ ] Add RTL CSS to all SCSS files
- [ ] Remove fixed `.app-main { height: 550px }` — use `dvh`
- [ ] Replace `console.error()` with `GbConsoleService`

### Week 3 — P1 Fixes (Functionality / Config)
- [ ] Implement `shareEntity()` via `MatDialog` (copy-link or email composer)
- [ ] Implement `deleteEntity()` via `MatDialog` confirmation
- [ ] Extract entity type config to JSON asset
- [ ] Extract layout configs (voucher, etc.) to JSON assets
- [ ] Add `@Input() mode: 'view' | 'print' | 'email'` with mode-specific rendering
- [ ] Add print stylesheet (`@media print`)

### Week 4 — P2 Fixes (Polish / Accessibility)
- [ ] Add ARIA labels to all buttons and interactive elements
- [ ] Add `scope="col"` to table headers
- [ ] Add `aria-live` to loading/error states
- [ ] Add `prefers-reduced-motion` guards on animations
- [ ] Remove 100+ `!important` from entity-viewer SCSS
- [ ] Add pagination for array/table fields
- [ ] Write unit tests for `EntityLayoutService` (start with `formatValue()`)
- [ ] Write component tests for `field-renderer`

---

## Quick Wins (< 1 hour each)

- [ ] Add `trackBy` to all `*ngFor` loops
- [ ] Remove commented-out `ngOnInit()` in `entity-viewer.component.ts:28`
- [ ] Delete mock `getTotalFields()` — return real count or remove display
- [ ] Hoist status map to readonly class constant (not recreated each call)
- [ ] Hoist `Intl.NumberFormat` to service-level constant
- [ ] Add empty `describe()` blocks to spec files to unblock CI
