# GbFormViewer — Deep Analysis Report

**Date:** 2026-02-24
**Scope:** `features/gbformviewer/` + `features/gblayout/service/gbformaction.service.ts` + `features/gblayout/dbservice/gbformaction.db.service.ts`
**Role:** Core component — dynamic lazy loader and permission gateway for all 619 entity-level data-entry/view screens across the 39 MFEs.

---

## File Inventory

| File | Lines | Purpose |
|------|-------|---------|
| `gbformviewer.component.ts` | 306 | Main component |
| `gbformviewer.component.html` | 2 | Template |
| `gbformviewer.component.scss` | 0 | Empty |
| `gbformviewer.component.spec.ts` | 0 | Empty |
| `gbformviewer.model.ts` | 76 | Interfaces |
| `gbformviewerurl.ts` | 1,664 | Remote module registry (619 entries) |
| `localgbformviewer.url.ts` | 671 | Local dev registry (338 entries) |
| `gbformaction.service.ts` | 68 | Permission + edit-state service |
| `gbformaction.db.service.ts` | 41 | HTTP layer |

---

## Critical Issues (Fix Immediately)

### C1 — Raw Nested Subscribes Without Cleanup (Memory Leak)
**File:** `gbformviewer.component.ts:163-223`, `243-301`
**Severity:** Critical

Both `loadLazyComponentbuild()` and `loadLazyComponentlocal()` contain raw `.subscribe()` calls with no `takeUntil`, `take(1)`, or `unsubscribe` mechanism. The outer subscribe (RolesandRights) wraps an inner subscribe (CodeDefine). If the user switches tabs quickly, multiple in-flight HTTP responses will all complete and attempt to set state on a potentially destroyed or re-navigated component.

```typescript
// Current — dangerous: outer subscribe wraps inner subscribe, no cleanup
this.formservice.RolesandRights(...).subscribe((RolesandRights) => {
  ...
  this.formservice.CodeDefine(this.EntityCode).subscribe(res => {
    this.MenuRights.set(MenuDetail);  // may fire after tab is closed
    this.lazyComponent = loadedComponent;
    this.cdr.detectChanges();
  });
});
```

**Fix:** Use `switchMap` with `takeUntilDestroyed()` (Angular 16+), or at minimum add `take(1)` on both observables and cancel on new tab selections.

---

### C2 — Missing `ChangeDetectionStrategy.OnPush`
**File:** `gbformviewer.component.ts:15`
**Severity:** Critical (CLAUDE.md violation)

The component decorator has no `changeDetection` property. For a component rendered inside every tab, this means Angular runs full change detection on every CD cycle for this component and any ancestor that triggers it. This is particularly expensive given it hosts dynamically loaded components.

---

### C3 — Keyboard Command Loop (Double-Processing Risk)
**File:** `gbformviewer.component.ts:85-97`, `109-130`
**Severity:** Critical — functional correctness

Two separate command channels are both active simultaneously:
1. PubSub: `notificationService.subscribe(COMMAND_PALETTE_FORM_CHANNEL, ...)` → calls `handleKeyboardCommand()`
2. Signal effect: `effect(() => sharedService.keyboardCommand())` → calls `handleKeyboardCommand()`

`handleKeyboardCommand()` then calls `sharedService.emitKeyboardCommand()`, which sets the same signal being observed by channel (2).

**Flow when pubsub fires:**
1. PubSub callback → `handleKeyboardCommand('Save')`
2. `emitKeyboardCommand('Save')` → sets `keyboardCommand` signal
3. Signal effect fires → `handleKeyboardCommand('Save')` **again**
4. `emitKeyboardCommand('Save')` → signal already 'Save', no new reaction
5. `setTimeout` resets signal to `''`
6. Effect fires for `''` — guard prevents action

Result: Every command arriving via pubsub is processed twice. Child components that also listen to `keyboardCommand` will receive two events per action. For Save, this means two save requests submitted.

---

### C4 — Silent Error Handling
**File:** `gbformviewer.component.ts:224-225`, `303-305`
**Severity:** Critical — user-invisible failures

```typescript
} catch (err) {
  // completely empty
}
```

When a remote MFE fails to load (network error, federation config mismatch, version mismatch), the user sees a blank screen with no feedback. This is the most user-facing failure mode for a core component.

---

### C5 — No Unit Tests
**File:** `gbformviewer.component.spec.ts`
**Severity:** Critical for a core infrastructure component

The spec file is empty. This component controls access rights, component loading, and command routing for every screen in the application. Zero test coverage.

---

## High-Priority Issues

### H1 — Massive Code Duplication (DRY Violation)
**File:** `gbformviewer.component.ts:143-226`, `228-306`

`loadLazyComponentbuild()` and `loadLazyComponentlocal()` are ~90% identical. The 60-line block handling RolesandRights fetch → CodeDefine fetch → MenuDetail assembly → Injector creation → `cdr.detectChanges()` is copy-pasted verbatim. The only differences:

| Aspect | build | local |
|--------|-------|-------|
| Component loading | `await loaderFn()` | `await component()` |
| Class extraction | Direct (`loadedComponent`) | `component[Object.keys(component)[0]]` |
| Error check | `if (!loadedComponent?.ɵcmp)` | None |

A shared private method `private setupMenuAndRender(loadedComponent: Type<unknown>)` would eliminate the duplication entirely.

---

### H2 — `lazyComponent` and `lazyInjector` Are Not Signals
**File:** `gbformviewer.component.ts:24`, `27`

```typescript
lazyComponent!: any;
lazyInjector!: Injector;
```

These are plain class properties. With OnPush CD (once added), changes to these won't be detected without `cdr.detectChanges()`. That's why `cdr.detectChanges()` is called manually at lines 216 and 294. Converting to `signal<Type<unknown> | null>(null)` and `signal<Injector | null>(null)` would allow proper reactivity and remove the `ChangeDetectorRef` dependency entirely.

---

### H3 — String Injection Tokens (CLAUDE.md Violation)
**File:** `gbformviewer.component.ts:200`, `201`, `203`

```typescript
{ provide: 'selectedId', useValue: ... },
{ provide: 'MenuRights', useValue: ... },
{ provide: 'DrillDownDetails', useValue: ... },
```

Magic string tokens have no type safety, no IDE autocompletion, and will silently produce `null` if the spelling differs between provider and consumer. Must be replaced with typed `InjectionToken<T>`.

```typescript
// Correct pattern
export const MENU_RIGHTS_TOKEN = new InjectionToken<RolesandRights>('MenuRights');
export const SELECTED_ID_TOKEN = new InjectionToken<{ SelectedId: string }>('selectedId');
export const DRILL_DOWN_TOKEN = new InjectionToken<IDrillDownDetails>('DrillDownDetails');
```

---

### H4 — `MenuRights` Snapshot Passed to Injector (Stale Data)
**File:** `gbformviewer.component.ts:201`, `279`

```typescript
{ provide: 'MenuRights', useValue: this.MenuRights() },
```

`this.MenuRights()` reads the signal value at injection time, producing a plain object snapshot. If `MenuRights` is later updated (e.g., after a permission refresh), child components will hold stale permissions. Passing the signal itself (`this.MenuRights` — without calling it) would allow children to react to updates.

---

### H5 — No Permission Caching (Redundant HTTP Calls on Tab Switch)
**File:** `gbformviewer.component.ts:163`, `243`

Every tab activation triggers two HTTP calls:
1. `RolesandRights` (MenuId) — fetches the full permission matrix
2. `CodeDefine` (EntityCode) — fetches auto-numbering config

For the same menu opened and closed repeatedly (common workflow: edit record, return, re-open), these calls are made every time. The results are deterministic for a session. Caching per `MenuId` in `FormActionservice` would eliminate most of these calls.

---

### H6 — Dialog Width Violates Responsive Standards
**File:** `gbformviewer.component.ts:149-154`, `233-239`

```typescript
this.dialog.open(GbDialogBoxComponent, {
  width: '600px',  // CLAUDE.md violation
  // maxWidth missing
});
```

Per CLAUDE.md: `min(600px, 95vw)` with `maxWidth: '95vw'`. On mobile and small viewports, a fixed 600px dialog overflows.

---

### H7 — `any` Types Throughout
**Severity:** High — TypeScript strict mode violation

Violations found:
- `lazyComponent!: any` — should be `Type<unknown> | null`
- `FormViewerComponentPathbuild = FormViewerComponentPath as any` — unnecessary cast; type is already `typeof FormViewerComponentPath`
- `RolesandRights.MenuObjectModelName: any` — should be `string`
- `RolesandRights.MenuObjectModelFileLocation: any` — should be `string`
- `RolesandRights.MenuWebFormObjectModelNames: any` — should be `string`
- `RolesandRights.CodeDefineGenerationType: any` — should be `number`
- `FormActionservice.isMenuEditable = signal<any>([])` — should be `signal<Array<{id: string, editable: boolean}>>([])`
- `FormActionservice.isMenuReset = signal<any>([])` — same
- `MenuDetail` implicitly typed `any` from `JSON.parse(JSON.stringify(...))`
- `Responsedata.ErrorBody: any`, `ErrorInnerException: any`, `ResponseObject: any`

---

### H8 — `ChangeDetectorRef` Usage (Anti-Pattern with Signals)
**File:** `gbformviewer.component.ts:25`, `216`, `294`

Per CLAUDE.md: "No ChangeDetectorRef — signals handle CD automatically." The `cdr.detectChanges()` calls are necessary only because `lazyComponent` and `lazyInjector` are plain properties, not signals. Fix H2 first; H8 resolves automatically.

---

## Medium-Priority Issues

### M1 — `console.log` Calls (CLAUDE.md Violation)
**File:** `gbformviewer.component.ts:165`, `166`, `187`

```typescript
console.log('MenuDetail--IN--Formviewre', MenuDetail);
console.log('TabDetails--Formviewre', this.TabDetails);
console.log('MenuDetail--Formviewre', MenuDetail);
```

These log `MenuDetail` which contains role/permission data and tab information. Replace with `GbConsoleService`.

---

### M2 — URL Slug Stripping Is Fragile
**File:** `gbformviewer.component.ts:135`, `138`

```typescript
this.TabDetails.MenuDetails.WebFormSecondURL.replace('/', '').replace('/', '')
```

Calls `.replace('/', '')` twice — this only removes the first two `/` characters encountered (not all). If `WebFormSecondURL` is `/admin/yearendposting`, result is `adminyearendposting` (correct by coincidence). But `/admin/master/yearend` becomes `adminmaster/yearend` (wrong). Use `.replace(/\//g, '')` to strip all slashes, or better, use a dedicated URL-to-key normalization function.

---

### M3 — `JSON.parse(JSON.stringify())` for Deep Copy
**File:** `gbformviewer.component.ts:164`, `244`

Using JSON round-trip for deep copying the `responseValue`. This:
- Silently drops `undefined` fields
- Fails on `Date`, `RegExp`, `function` values
- Is slower than `structuredClone()` (available in all modern browsers)

Replace with `structuredClone(RolesandRights.responseValue)`.

---

### M4 — `isMenuEditable`/`isMenuReset` Use O(n) Array Lookup
**File:** `gbformaction.service.ts:37-48`

```typescript
isMenuEditable = signal<any>([]);
// on every update:
const existingIndex = data.findIndex((item) => item.id === MenuId);
```

This grows linearly as more menus are opened. With potentially 50+ tabs in a session, every `setMenuEditable` call scans the entire array. Use `signal<Record<string, boolean>>({})` or `signal<Map<string, boolean>>(new Map())` for O(1) lookup.

---

### M5 — `BizTransactionClassId` Parameter Always `-1`
**File:** `gbformviewer.component.ts:163`, `243`; `gbformaction.service.ts:51-54`

```typescript
this.formservice.RolesandRights(this.TabDetails.MenuDetails.Id, -1)
// service builds:
let params = "/?MenuId=" + MenuId + "&BizTransactionTypeId=" + BizTransactionClassId;
```

`BizTransactionClassId` is always `-1`. Either the API ignores it (dead parameter) or this is a bug that causes rights to always be fetched with no class filter. The parameter should be removed from the method signature if unused, or populated from `TabDetails.MenuDetails.BizTransactionClassId`.

---

### M6 — `Tab` Interface Defined in a Component File
**File:** `features/gbtabcontainer/tabcontainer/gbtabcontainer.component.ts:44-53`

The `Tab` interface is exported from a `.component.ts` file and imported by `gbformviewer.component.ts`. Interfaces belong in dedicated model files (e.g., `features/gbtabcontainer/tabcontainer/gbtabcontainer.model.ts`).

---

### M7 — `FormActiondbservice` Uses Constructor Injection
**File:** `gbformaction.db.service.ts:9`

```typescript
constructor(public http: GBHttpService) { }
```

CLAUDE.md requires `inject()`. Should be:
```typescript
private http = inject(GBHttpService);
```

Also, `public` visibility on `http` exposes the HTTP service unnecessarily.

---

### M8 — Leaky `FormViewerComponentPathlocal` Bundled in Production
**File:** `gbformviewer.component.ts:10`

```typescript
import { FormViewerComponentPathlocal } from "./localgbformviewer.url";
```

This import is unconditional. The 671-line local registry file (338 dynamic `import()` expressions) is included in the production bundle. While individual lazy imports are code-split, the key-to-path map itself is bundled and parsed in production. This adds unnecessary bytes and parse time.

Use a dynamic import with `isDevMode()` to conditionally import:
```typescript
// Conceptually:
if (isDevMode()) {
  const { FormViewerComponentPathlocal } = await import('./localgbformviewer.url');
  ...
}
```

Or structure it into environment-specific files resolved at build time.

---

### M9 — No Loading State During Remote Module Fetch
**File:** `gbformviewer.component.ts:143-226`

When switching to a tab whose remote MFE hasn't been loaded yet (first time, slow network), the user sees a blank area. There is no spinner, skeleton screen, or progress indicator. For a core data-entry component, this is a UX gap. A simple `isLoading = signal(false)` with a skeleton/spinner in the template would significantly improve perceived performance.

---

### M10 — Injector Leaks on Rapid Tab Switching
**File:** `gbformviewer.component.ts:198-212`, `276-290`

`Injector.create()` is called on each `ngOnChanges`. Old injectors are not disposed. Angular injectors hold references to their parent injector chain. While the GC eventually reclaims them if no strong references remain, if child components hold references (via `lazyInjector`), repeated tab switching creates injector chains that are not explicitly cleaned up.

Destroy the previous `lazyInjector` before creating a new one:
```typescript
// Before creating new injector:
if (this.lazyInjector && 'destroy' in this.lazyInjector) {
  (this.lazyInjector as any).destroy();
}
```

---

### M11 — Component Key Naming Is Inconsistent
**File:** `gbformviewerurl.ts`, `localgbformviewer.url.ts`

Registry keys use multiple conventions with no enforced standard:
- `adminyearendposting` — all lowercase
- `FAMassetschedule` — uppercase module prefix
- `hrmsAttendenceAdjustmentEntry` — camelCase with typo ("Attendence")
- `GbMySettingComponent` — PascalCase with full suffix
- `crmsalesContactList` — mixed module+feature camelCase

This makes lookups fragile (case sensitivity issues) and the registry hard to search.

---

### M12 — "Attendence" Typo in Registry Key
**File:** `gbformviewerurl.ts`, `localgbformviewer.url.ts`

```typescript
hrmsAttendenceAdjustmentEntry: () => loadRemoteModule(...)
```

"Attendence" → "Attendance". If the backend `WebFormSecondURL` has the correct spelling, this key will never match, silently showing the "Component not available" dialog.

---

## Low-Priority / Maintainability Issues

### L1 — 1,664-Line Static Registry File (Maintainability)
**File:** `gbformviewerurl.ts`

619 manually maintained entries in a single file. Every new component requires:
1. A new entry in `gbformviewerurl.ts`
2. A corresponding entry in `localgbformviewer.url.ts`
3. The key must match `WebFormSecondURL` from the backend menu config

This is error-prone and doesn't scale. Consider a code generator script that reads the backend menu config and regenerates the registry, or a convention-based registry using consistent naming patterns.

---

### L2 — Commented-Out Old Import Paths
**File:** `gbtabcontainer.component.ts:30-32`; `gbformviewerurl.ts:1640-1662`

Multiple blocks of commented-out old import paths from previous module structures remain. These add noise and should be removed.

---

### L3 — `constructor()` Not Needed in Services
**File:** `gbformaction.service.ts:9`

```typescript
constructor() { }
```

Empty constructor is unnecessary noise. Remove it.

---

### L4 — `ɵcmp` Private API Check
**File:** `gbformviewer.component.ts:159`

```typescript
if (!loadedComponent?.ɵcmp) {
  throw new Error('Loaded module does not appear to be a Component');
}
```

`ɵcmp` is an Angular-internal property (prefixed with `ɵ` = internal/unstable). It can change across Angular versions without notice. Use `isStandaloneComponent()` (Angular 17+) or check for the component decorator metadata via `ng.getComponent` in dev mode only.

---

### L5 — Template Uses Deprecated Structural Directive Syntax
**File:** `gbformviewer.component.html`

```html
<ng-container *ngComponentOutlet="lazyComponent; injector: lazyInjector"></ng-container>
```

Angular 20 block syntax equivalent:
```html
@if (lazyComponent) {
  <ng-container [ngComponentOutlet]="lazyComponent" [ngComponentOutletInjector]="lazyInjector" />
}
```

Minor — the old syntax still works in Angular 20.

---

### L6 — Missing `MenuObjectModelName` in Local Build Path
**File:** `gbformviewer.component.ts:244-253`

The local build path sets `MenuObjectModelName`, `MenuObjectModelFileLocation`, `MenuWebFormObjectModelNames` — but the build path (lines 168-174) sets the same fields in the same order. The duplication means if a new field is added to one, the other must be manually kept in sync.

---

## Functional Issues

### F1 — Permission Check Runs After Component Load (Security Gap)
**File:** `gbformviewer.component.ts:143-226`

**Current flow:**
1. Component is loaded (`await loaderFn()`)
2. Then `RolesandRights` is fetched

The component is resolved before rights are known. If `RolesandRights` fails (network error — caught silently), the component is never rendered — but there's no clear error state. More critically, if rights are fetched but the component should NOT be accessible (`Allow !== 'Y'`), it isn't checked before loading. The rights check and display logic belongs in child components (which is a distributed responsibility pattern), but there's no centralized enforcement here.

**Recommendation:** Fetch `RolesandRights` first; if `Allow !== 'Y'`, show an access-denied message without loading the component at all.

---

### F2 — `setMenuEditable(menuId, true)` Always Sets `true`
**File:** `gbformviewer.component.ts:192`, `268`

```typescript
this.formservice.setMenuEditable(menuId, true);
```

The menu is always set to editable (`true`) on load. If the user's rights say `Update: 'N'`, the form should start in view-only mode. The editable state should derive from `MenuRights.Update`, not default to `true`.

---

### F3 — `Tab` Interface Has `TabId` and `id` — Redundancy
**File:** `gbtabcontainer.component.ts:44-53`; `gbformviewer.component.ts:167`, `190`

The `Tab` interface has both `TabId: string` and `id: string`. The component uses `this.TabDetails.id` (not `TabId`). Usage: `MenuDetail.TabId = this.TabDetails.id`. The `TabId` property on `Tab` is never used in `GbFormViewerComponent`. This redundancy is confusing.

---

### F4 — `DrillDownDetails` Passed Even for Non-Drill-Down Navigation
**File:** `gbformviewer.component.ts:203-209`

```typescript
{ provide: 'DrillDownDetails', useValue: {
  DrillDownMenuId: this.TabDetails.MenuDetails.CurrentMenuId,
  DrillDownIndex: this.TabDetails.MenuDetails.DrillDownIndex,
  DrillDownFieldId: this.TabDetails.MenuDetails.DrillDownFieldId
}}
```

`DrillDownMenuId`, `DrillDownIndex`, and `DrillDownFieldId` are populated even for normal (non-drill-down) tab navigations. Child components must defensively check for null/empty values. There's no flag indicating whether this tab is a drill-down context or a primary navigation.

---

### F5 — `GcmTypeId` / `GcmTypeName` Added to `MenuDetail` but Not in `RolesandRights` Interface
**File:** `gbformviewer.component.ts:171-172`

```typescript
MenuDetail.GcmTypeId = this.TabDetails.MenuDetails.GcmTypeId || -1;
MenuDetail.GcmTypeName = this.TabDetails.MenuDetails.GcmTypeName || '';
```

These fields are added to `MenuDetail` (which is a copy of `RolesandRights`) but `GcmTypeId`/`GcmTypeName` don't exist in the `RolesandRights` interface. This means TypeScript is silently allowing `any`-typed property assignment, and child components using the typed `MenuRights` token won't see these fields in their type definitions.

---

### F6 — `CodeDefineGenerationType` in Both Component and `MenuDetail`
**File:** `gbformviewer.component.ts:29-30`, `180-184`

```typescript
CodeDefineGenerationType: number = 0   // class property
// ...
this.CodeDefineGenerationType = res.responseValue[0].CodeDefineGenerationType;
MenuDetail.CodeDefineGenerationType = this.CodeDefineGenerationType;
```

The class property `CodeDefineGenerationType` is set then immediately copied to `MenuDetail`. The class-level property is redundant — only `MenuDetail.CodeDefineGenerationType` is used downstream (via `MenuRights` signal).

---

### F7 — No Tab Deactivation / Cleanup Signal to Child
**File:** `gbformviewer.component.ts` — missing feature

When a user switches away from a tab:
1. `ngOnChanges` fires with new `TabDetails`
2. A new component begins loading
3. The old component is still rendered until the new one is ready
4. The old component's in-flight HTTP requests (if any) are not cancelled

There's no mechanism to signal to the current child component that it's being replaced (e.g., to save unsaved state, cancel in-flight requests, or show a "You have unsaved changes" prompt).

---

### F8 — Missing Null Guard for `WebFormSecondURL`
**File:** `gbformviewer.component.ts:133-140`

```typescript
if (changes['TabDetails'] && this.TabDetails.MenuDetails != undefined) {
  if (isDevMode()) {
    this.loadLazyComponentlocal(
      this.TabDetails.MenuDetails.WebFormSecondURL.replace('/', '').replace('/', '')
    );
```

`WebFormSecondURL` itself is not null-checked. If `MenuDetails` is defined but `WebFormSecondURL` is `null` or `undefined`, this throws a runtime error.

---

## Architecture Observations

### A1 — GbFormViewer is the Single Point of Failure for All Data Entry

Every form in the system routes through this component. It controls:
- Component loading (federation)
- Permission fetching (RolesandRights)
- Code generation type (CodeDefine)
- Context injection (Injector.create)
- Keyboard command routing

This makes it critical infrastructure. Its empty test suite (C5) and silent error handling (C4) are especially concerning given this role.

---

### A2 — Two Parallel Registries Must Be Manually Synchronized

`gbformviewerurl.ts` (619 entries) and `localgbformviewer.url.ts` (338 entries) must be kept in sync manually. The local registry has 281 fewer entries than the production one — those components cannot be tested locally. There is no mechanism to detect registry drift.

---

### A3 — `FormActionservice` Mixes Unrelated Concerns

The service handles:
- Form edit state (`isFormEditable`, `setFormEditable`)
- Per-menu edit state (`isMenuEditable`)
- Permission fetching (`RolesandRights`)
- Code generation config (`CodeDefine`)
- Draft saving (`savedraftservice`)
- Google Translate (`googlapitranslateservice`)

These are at least 3 separate concerns. Consider splitting into:
- `FormPermissionService` — RolesandRights, CodeDefine
- `FormEditStateService` — editable/reset signals
- Moving `savedraftservice` and `googlapitranslateservice` to domain-specific services

---

### A4 — Keyboard Command Architecture Is Inverted

The intended flow is: keyboard/command-palette → gbformviewer → child form component.

Current implementation: gbformviewer reads from `sharedService.keyboardCommand` (set by external code) and re-emits on the same signal. Child components must also listen to the same signal directly. This means gbformviewer is redundant as a relay — child components could listen directly. Or, if gbformviewer is meant to intercept (e.g., to check if the active tab is this form), the relay logic is incomplete (no active tab check).

---

## Summary Priority Matrix

| Priority | Count | Items |
|----------|-------|-------|
| Critical | 5 | C1–C5 |
| High | 8 | H1–H8 |
| Medium | 12 | M1–M12 |
| Low | 6 | L1–L6 |
| Functional | 8 | F1–F8 |
| Architecture | 4 | A1–A4 |

---

## Recommended Fix Order

1. **C4** — Add error handling with user-visible feedback
2. **C1** — Fix nested subscribes → `switchMap` + `takeUntilDestroyed()`
3. **C2** — Add `ChangeDetectionStrategy.OnPush`
4. **C3** — Fix keyboard command double-processing (remove one channel or deduplicate)
5. **H1** — Refactor duplicated load logic into shared private method
6. **H2** — Convert `lazyComponent`/`lazyInjector` to signals (enables C2 fix, removes H8)
7. **H3** — Replace string injection tokens with typed `InjectionToken<T>`
8. **F1** — Check `Allow` flag before loading component
9. **F2** — Derive `setMenuEditable` from actual rights, not hardcoded `true`
10. **H5** — Add RolesandRights + CodeDefine caching per MenuId
11. **M1** — Replace `console.log` with `GbConsoleService`
12. **H6** — Fix dialog width to `min(600px, 95vw)` + `maxWidth: '95vw'`
13. **M2** — Fix URL slug stripping (use regex)
14. **M3** — Replace `JSON.parse(JSON.stringify)` with `structuredClone()`
15. **C5** — Add unit tests for permission check, component loading, error states, keyboard handling
