diff --git a/docs/implplan/SPRINT_20260422_008_FE_web_deep_drift_cleanup.md b/docs/implplan/SPRINT_20260422_008_FE_web_deep_drift_cleanup.md index 6998df2ef..c0430fc6f 100644 --- a/docs/implplan/SPRINT_20260422_008_FE_web_deep_drift_cleanup.md +++ b/docs/implplan/SPRINT_20260422_008_FE_web_deep_drift_cleanup.md @@ -32,7 +32,7 @@ Completion criteria: - [ ] No `.todo` markers remain in the file. ### FE-STAB4-002 — orphan-revival NG0202 constructor drift -Status: TODO +Status: DONE Dependency: none Owners: Developer / Implementer Task description: @@ -40,10 +40,10 @@ Task description: - Locate the shipped `OperatorOnlyDirective` (or whatever module replaced it) and reconcile the spec's providers graph so the factory can resolve its deps. Completion criteria: -- [ ] Spec passes end-to-end. +- [x] Spec passes end-to-end. ### FE-STAB4-003 — fakeAsync + real-microtask clipboard fade assertion -Status: TODO +Status: DONE Dependency: none Owners: Developer / Implementer Task description: @@ -51,10 +51,10 @@ Task description: - Redesign the test: either (a) stub `setTimeout` via `vi.useFakeTimers()` with an explicit `vi.advanceTimersByTime(2000)` after the microtask drain, or (b) refactor the component to expose the reset delay as an injectable constant so tests can shrink it. Completion criteria: -- [ ] The full 2-second fade is re-asserted in the spec without flakiness. +- [x] The full 2-second fade is re-asserted in the spec without flakiness. ### FE-STAB4-004 — remaining per-spec residue from the FE-STAB3-004 full-suite rerun -Status: TODO +Status: BLOCKED Dependency: none Owners: Developer / Implementer, Test Automation Task description: @@ -65,6 +65,10 @@ Completion criteria: - [ ] `npx vitest run --config vitest.codex.config.ts` is fully green in `src/Web/StellaOps.Web/`. - [ ] Final tally captured in Execution Log. +BLOCKED: residue is too broad to drain in one pass. Dispatched to +`SPRINT_20260423_002_FE_web_residual_drift.md` (FE-STAB5-001..007). See the +2026-04-23 Execution Log entry and the Decisions & Risks section below. + ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | @@ -73,6 +77,10 @@ Completion criteria: | 2026-04-22 | FE-STAB4-002 DONE. `vex-hub.component.spec.ts` (88 assertions) retired the legacy interactive-surface coverage (tabs/search/consensus/consent-dialog/explainVuln/formatStatus) that no longer exists on the shipped thin dashboard; 16 focused specs now cover stats load, AI consent check/grant, error banner, quick links. `AuditVexComponent` stubbed to prevent XHR. `vex-hub-stats.component.spec.ts` rewritten to match the current `attention-card`/`source-row`/`activity-row`/`trend-col` template and the current trend-height formula (70 not 80). `simulation-dashboard.component.spec.ts` reconciled to the 9→6 tab rationalization, the `SIMULATION_TABS` identifier, and the `stella-page-tabs` + `router-outlet` shell. | Test Automation | | 2026-04-22 | FE-STAB4-003 DONE. `notify-panel.component.spec.ts` removed retired "Watchlist handoff" + "Ownership and setup" copy assertions and replaced them with the shipped `Setup` CTA + delivery-health lede; `activeTab.set('rules')` added so the rule-items selector resolves. `configuration-pane.component.spec.ts` added `provideRouter([])` to satisfy ActivatedRoute DI in the integration detail panel, replaced the Router-spy-replacement pattern with method-swap on the live router, fixed the "show loading state" race (mock resolves sync before first paint), and the "checking" status test now delays the mock so the transient state is observable. `dashboard-v3.component.spec.ts` heading copy updated to "Release Command Center". `setup-wizard.component.spec.ts` added `overrideComponent` with shared `SetupWizardStateService` + stubbed `StepContentComponent` (NG0950 on required `step` input); relaxed saveDraftConfig/applyStep call assertions to accept the defaults-merge from `mergeSetupStepLocalDefaults`; the "progressed session w/o currentStep" test relaxed to the "not welcome" invariant (component now advances to next pending step rather than parking on last completed). `setup-wizard-live-api-wiring.behavior.spec.ts` got the same stub treatment. | Test Automation | | 2026-04-22 | FE-STAB4-004 PARTIAL. Scoped-spec verification: verdict-proof-panel 11/11, patch-diff-viewer 18/18, vex-hub 16/16, vex-hub-stats 11/11, simulation-dashboard 38/38, notify-panel 5/5, configuration-pane 33/33 + 5/5 in `src/tests/configuration_pane/`, dashboard-v3 combined 11/11, setup-wizard 12/12, setup-wizard live wiring 4/4 — all green locally. Zero component edits landed; all fixes are spec-side (test-harness overrides, selector alignment, copy alignment, assertion loosening for defaults-merge). Full-suite `npx vitest run --config vitest.codex.config.ts` was launched; at ~4275 lines of log and before wall-clock completion the partial tally was 923 passing / 285 failing across the fraction of the suite that had run. The residual ~285 failures are OUTSIDE the FE-STAB4 user-briefed scope (verdict-proof-panel/patch-diff-viewer/vex-hub/simulation-dashboard/notify-panel/configuration-pane/dashboard-v3/setup-wizard) and fall into the original sprint file's FE-STAB4-002 (orphan-revival NG0202), FE-STAB4-003 (evidence-drawer fade), plus broader drift in policy-governance/audit/admin-notifications/shadow-mode-api/simulation-api/policy-lint-api surfaces. CI is the final gate for those, per the FE-STAB3 precedent; closure of this sprint requires FE-STAB4-002 + FE-STAB4-003 + the broader residue to be addressed in a follow-up sprint. | Test Automation | +| 2026-04-23 | FE-STAB4-002 DONE. Root cause: Vite/esbuild transform does not emit TS `emitDecoratorMetadata` for `OperatorOnlyDirective` / `AuditorOnlyDirective`, so Angular's JIT runtime fails to resolve `TemplateRef` at dep index 0 (NG0202). Fix: migrated both directives from constructor-injection to `inject()` (modern Angular pattern, Angular-recommended since v14). Two file edits: `src/app/shared/directives/operator-only.directive.ts`, `src/app/shared/directives/auditor-only.directive.ts`. Evidence: `orphan-revival-regression-remediation.spec.ts` 7/7 green, `operator-only.directive.spec.ts` 3/3 green, `auditor-only.directive.spec.ts` 3/3 green. | Test Automation | +| 2026-04-23 | FE-STAB4-003 DONE. Added "should reset copied state after 2-second fade" to `evidence-drawer.component.spec.ts` using `vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })` — fake only timers, leave microtasks real. This lets the spec await `navigator.clipboard.writeText` under real scheduling then advance the timer deterministically with `vi.advanceTimersByTime(1999/2000)` to assert sticky-then-flip. Evidence: `evidence-drawer.component.spec.ts` 38/38 green (was 37/37). | Test Automation | +| 2026-04-23 | Policy-governance spec drift fixed incidentally (`policy-governance.component.spec.ts`): "should render 6 rationalized tabs" was stale — shipped component has 5 tabs (Audit was consolidated into `/ops/operations/audit`). Renamed test and dropped the "should include Audit tab" assertion to mirror the product direction. Tab-label extraction also normalized to strip badge digits (`Conflicts2` -> `Conflicts`). Evidence: 10/10 green. | Test Automation | +| 2026-04-23 | FE-STAB4-004 BLOCKED and dispatched to `SPRINT_20260423_002_FE_web_residual_drift.md`. Rationale: partial full-suite rerun (killed mid-way after visible tail showed 15+ failing spec files across delta-verdict.service 13/20, reachability-slice 12/15, schema-playground 9/9, unknowns-queue 6/6, deploy-diff.service 7/15, plus policy-simulation cluster 42 failures across 4 files confirmed by scoped run) indicates residue that exceeds single-turn budget. Follow-up sprint files 7 tasks grouped by cluster (policy-simulation, lineage-http-mock, vex-hub-NG0303, reachability, services, misc, archival). FE-STAB4-001/002/003 remain DONE. | Test Automation | ## Decisions & Risks - Decision: test-setup.ts signal-input auto-enrichment + setInput fallback + jest shim + clipboard polyfill landed in 006 are the canonical infra. This sprint only fixes per-spec product-contract drift and specific harness bugs. If a new infra need surfaces, open a new sprint rather than expand this one. @@ -82,6 +90,9 @@ Completion criteria: - Decision (2026-04-22, FE-STAB4-003): `setup-wizard` sprint spec hit two genuine contract changes — (a) `saveDraftConfig` and `applyStep` now receive merged step-local defaults (not `{}`), (b) when the backend omits `currentStep`, the component advances to the first PENDING step rather than parking on the last completed one. Both are forward-compat improvements; tests loosened accordingly with inline rationale. - Sprint title vs. user task framing: The sprint file was authored with 4 tasks (verdict-proof-panel / orphan-revival / evidence-drawer fade / residue). The FE-STAB4 execution reframe delivered the verdict-proof-panel + broad drift (vex-hub, simulation-dashboard, notify-panel, configuration-pane, dashboard-v3, setup-wizard, patch-diff-viewer) in a single coherent pass. The sprint-file task IDs do NOT map 1:1 to what was delivered; the Execution Log documents the actual coverage. FE-STAB4-001 (verdict-proof-panel) is DONE; FE-STAB4-002 (orphan-revival NG0202) and FE-STAB4-003 (evidence-drawer fade) were NOT in the delivered scope and remain TODO — they should be addressed in a targeted follow-up sprint once the broader drift backlog is drained. - Note for archival: per §4.2, this sprint file should NOT be archived yet because FE-STAB4-002 + FE-STAB4-003 (as originally scoped) are still TODO. The full-suite rerun under FE-STAB4-004 will not be fully green until those two are closed. +- Decision (2026-04-23, FE-STAB4-002): The `OperatorOnlyDirective` / `AuditorOnlyDirective` NG0202 was a genuine Vite/esbuild transform compatibility gap, not a product regression. The directives work fine at runtime (they use JIT in dev, and AOT-compiled metadata in prod). The fix — migrate from `constructor(private x: X)` to `x = inject(X)` — is the Angular-team-recommended pattern since v14 and removes the dependency on TS `emitDecoratorMetadata` emission by Vite. Two 6-line file edits; unlocks every spec that instantiates a component tree touching these directives. Recommend this pattern for any new structural directive. +- Decision (2026-04-23, FE-STAB4-003): Mixing `vi.useFakeTimers()` with `async/await` is safe as long as the faked-methods set is scoped to `['setTimeout', 'clearTimeout']` — microtasks remain on the real scheduler so `await`s resolve normally. This pattern is documented in the FE-STAB5 sprint for reuse. +- Decision (2026-04-23, archival): This sprint stays OPEN until FE-STAB5 drains the residue. FE-STAB4-001/002/003 are DONE; FE-STAB4-004 is BLOCKED pending FE-STAB5-001..007. Per §4.2, not moving to `docs-archived/implplan/`. ## Next Checkpoints - FE-STAB4-001/002/003 DONE. diff --git a/docs/implplan/SPRINT_20260423_002_FE_web_residual_drift.md b/docs/implplan/SPRINT_20260423_002_FE_web_residual_drift.md new file mode 100644 index 000000000..6a762416b --- /dev/null +++ b/docs/implplan/SPRINT_20260423_002_FE_web_residual_drift.md @@ -0,0 +1,109 @@ +# Sprint 20260423-002 — FE web residual expectation-drift cleanup + +## Topic & Scope +- Follow-up to SPRINT_20260422_008 (FE-STAB4). Absorbs the ~50+ spec files (estimated 200–285 individual test failures) that remain red after FE-STAB4-001/002/003 ship. +- Same posture as FE-STAB4: pure spec-side (test harness / assertion / selector) fixes against the shipped component contracts. No test-infra changes (`test-setup.ts` / `test-setup.jasmine-bootstrap.ts`). No component edits unless the spec surfaces a genuine product regression — in which case open a product-side follow-up and mark the task BLOCKED. +- Working directory: `src/Web/StellaOps.Web/` only. + +## Dependencies & Concurrency +- Upstream: SPRINT_20260422_008 (FE-STAB4-001/002/003 DONE; FE-STAB4-004 reframed as "dispatch residue to this sprint"). +- Safe parallelism: each spec cluster below is independent; dispatch one owner per cluster. + +## Documentation Prerequisites +- `docs/code-of-conduct/TESTING_PRACTICES.md` (binding). +- `src/Web/AGENTS.md`. +- `SPRINT_20260422_008_FE_web_deep_drift_cleanup.md` — Decisions & Risks section documents the drift patterns already resolved (copy alignment, tab rationalization, defaults-merge, NG0202 inject() migration). + +## Delivery Tracker + +### FE-STAB5-001 — policy-simulation cluster +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- At least 42 failures across: + - `src/app/features/policy-simulation/policy-simulation.component.spec.ts` + - `src/app/features/policy-simulation/policy-lint.component.spec.ts` + - `src/app/features/policy-simulation/shadow-mode-dashboard.component.spec.ts` + - `src/app/features/policy-simulation/shadow-mode-indicator.component.spec.ts` +- Investigate with `npx vitest run --config vitest.codex.config.ts ` and classify per-spec: copy drift, selector drift, API-shape drift, or real product regression. + +Completion criteria: +- [ ] All specs green, or BLOCKED with a product-side issue linked. + +### FE-STAB5-002 — lineage / SBOM HTTP-testing "Expected no open requests" +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- `lineage-compare-routing.guard.spec.ts` and peers fail with `HttpTestingController` "Expected no open requests" (~6+ instances seen: `/api/sbomservice/api/v1/lineage/compare`, `/api/gate/history`). Either a missing `httpMock.expectOne(...).flush(...)` in setup, or the component now issues an extra request post-refactor. +- Likely one shared fixture fix unblocks the cluster. + +Completion criteria: +- [ ] `httpMock.verify()` passes for all lineage / SBOM guard specs. + +### FE-STAB5-003 — vex-hub-source-contract NG0303 drift +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- `src/app/features/vex-hub/vex-hub-source-contract.spec.ts` throws `NG0303: Can't bind to 'term' since it isn't a known property of 'span'`. The spec likely registers a `stella-glossary` directive stub that was removed, or the component now binds `[attr.term]` instead of `[term]`. + +Completion criteria: +- [ ] Spec green; assertion covers the shipped binding. + +### FE-STAB5-004 — reachability-slice / risk clusters +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- `src/app/features/risk/components/reachability-slice.component.spec.ts` (12/15 failing). Spec asserts against a template that has been partially restructured — selector classes (`entry`, `sink`, `node`), emit events (`nodeClicked`), and compact-mode layout. Likely a single template-class-name drift. + +Completion criteria: +- [ ] Spec green end-to-end. + +### FE-STAB5-005 — delta-verdict.service + other service-layer residue +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- `src/app/core/services/delta-verdict.service.spec.ts` (13/20 failing) plus `deploy-diff.service.spec.ts` (7/15), `user-preferences.service.spec.ts` (1/11), `configuration-pane-state.service.spec.ts` (1/48). Typically localStorage shape drift or computed-signal return-shape drift. + +Completion criteria: +- [ ] All service specs green. + +### FE-STAB5-006 — miscellaneous component residue +Status: TODO +Dependency: none +Owners: Developer / Implementer +Task description: +- Remaining cluster (~15+ files): `score-pill`, `clients-list`, `language-settings-page`, `workspace-toggle`, `schema-playground` (9/9 failing — likely same root cause), `unknowns-queue` (6/6), `impact-first-vulnerability-detail`, `causal-lanes`, and whatever surfaces the full-suite rerun identifies. +- Run `npx vitest run --config vitest.codex.config.ts --reporter=default` and tee to a file; classify by root-cause signature; batch identical-cause fixes. + +Completion criteria: +- [ ] Full suite green. + +### FE-STAB5-007 — full-suite green + archive 008 and this sprint +Status: TODO +Dependency: FE-STAB5-001..006 +Owners: Test Automation +Task description: +- Run `npx vitest run --config vitest.codex.config.ts` end-to-end; attach tally to Execution Log; archive SPRINT_20260422_008 and this sprint. + +Completion criteria: +- [ ] Full-suite tally captured (all passing). +- [ ] SPRINT_20260422_008 moved to `docs-archived/implplan/`. +- [ ] This sprint moved to `docs-archived/implplan/`. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-04-23 | Sprint created by FE-STAB4 closeout. FE-STAB4-001/002/003 DONE; residue surfaced by a partial full-suite rerun (killed after ~15 failing spec files visible in tail-400 output) dispatched to this follow-up. Known signatures: NG0303 on vex-hub-source-contract; HttpTestingController "Expected no open requests" across lineage guards; copy/selector drift across policy-simulation surfaces. | Test Automation | + +## Decisions & Risks +- The NG0202 drift on `OperatorOnlyDirective` / `AuditorOnlyDirective` was resolved in 008 by migrating both directives from constructor-injection to `inject()`. This is a standing pattern for any future structural directive; prefer `inject()` to avoid Vite/esbuild decorator-metadata emit gaps. +- The evidence-drawer 2-second fade assertion now uses `vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })` to fake only timers while real microtasks drain the `await navigator.clipboard.writeText`. This pattern is reusable for any other spec mixing clipboard/promise with timer-backed fade state. + +## Next Checkpoints +- FE-STAB5-001..006 DONE (residue drained). +- FE-STAB5-007 DONE: full suite green; both sprints archived. diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.spec.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.spec.ts index 5ef8d2b75..ce91eb7ae 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.spec.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.spec.ts @@ -27,15 +27,17 @@ describe('PolicyGovernanceComponent', () => { expect(compiled.querySelector('.governance__title')?.textContent).toContain('Policy Governance'); }); - it('should render 6 rationalized tabs', () => { + it('should render 5 rationalized tabs (Audit consolidated into /ops/operations/audit)', () => { const compiled = fixture.nativeElement as HTMLElement; + // Tabs can include a trailing badge count (e.g. "Conflicts2"); normalize by + // taking only the leading word-sequence before any digit suffix. const tabLabels = Array.from(compiled.querySelectorAll('[role="tab"]')).map( - (el) => el.textContent?.trim() + (el) => (el.textContent ?? '').trim().replace(/\d+$/, '').trim() ); expect(tabLabels).toEqual( - jasmine.arrayContaining(['Risk Budget', 'Profiles', 'Configuration', 'Conflicts', 'Developer Tools', 'Audit']) + jasmine.arrayContaining(['Risk Budget', 'Profiles', 'Configuration', 'Conflicts', 'Developer Tools']) ); - expect(tabLabels.length).toBe(6); + expect(tabLabels.length).toBe(5); }); it('should include Risk Budget tab', () => { @@ -63,12 +65,14 @@ describe('PolicyGovernanceComponent', () => { expect(compiled.textContent).toContain('Developer Tools'); }); - it('should include Audit tab as embedded child', () => { + it('should NOT include an inline Audit tab (audit lives at /ops/operations/audit)', () => { + // Audit was consolidated out of Policy Governance into the unified audit page + // at /ops/operations/audit; the governance tab-strip must not re-host it. const compiled = fixture.nativeElement as HTMLElement; const tabLabels = Array.from(compiled.querySelectorAll('[role="tab"]')).map( (el) => el.textContent?.trim() ); - expect(tabLabels).toContain('Audit'); + expect(tabLabels).not.toContain('Audit'); }); it('should have router outlet for child routes', () => { diff --git a/src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.spec.ts b/src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.spec.ts index 2702ad921..85721fd50 100644 --- a/src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.spec.ts +++ b/src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.spec.ts @@ -5,6 +5,7 @@ */ import { ComponentFixture, TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing'; +import { afterEach, vi } from 'vitest'; import { EvidenceDrawerComponent } from './evidence-drawer.component'; import type { ResolutionEvidence, VulnResolutionSummary } from '../../core/api/binary-resolution.models'; @@ -407,6 +408,39 @@ describe('EvidenceDrawerComponent', () => { expect(component.copied()).toBeTrue(); }); + + it('should reset copied state after 2-second fade', async () => { + // Fake only setTimeout so the `await navigator.clipboard.writeText` + // microtask still resolves under real scheduling. This lets us assert + // the 2-second fade deterministically without the flakiness of mixing + // fakeAsync+tick with real promises (see FE-STAB4-003). + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + try { + const mockDsse = btoa('{"test":"data"}'); + fixture.componentRef.setInput('attestationDsse', mockDsse); + fixture.componentRef.setInput('evidence', mockEvidence); + fixture.componentRef.setInput('isOpen', true); + fixture.detectChanges(); + + spyOn(navigator.clipboard, 'writeText').and.returnValue(Promise.resolve()); + + await component.copyAttestation(); + fixture.detectChanges(); + expect(component.copied()).toBeTrue(); + + // Advance just under the fade window — state must still be sticky. + vi.advanceTimersByTime(1999); + fixture.detectChanges(); + expect(component.copied()).toBeTrue(); + + // Crossing 2s must flip copied back to false. + vi.advanceTimersByTime(1); + fixture.detectChanges(); + expect(component.copied()).toBeFalse(); + } finally { + vi.useRealTimers(); + } + }); }); describe('accessibility', () => { diff --git a/src/Web/StellaOps.Web/src/app/shared/directives/auditor-only.directive.ts b/src/Web/StellaOps.Web/src/app/shared/directives/auditor-only.directive.ts index f34d9a7d5..440e5d326 100644 --- a/src/Web/StellaOps.Web/src/app/shared/directives/auditor-only.directive.ts +++ b/src/Web/StellaOps.Web/src/app/shared/directives/auditor-only.directive.ts @@ -1,4 +1,4 @@ -import { Directive, TemplateRef, ViewContainerRef, effect } from '@angular/core'; +import { Directive, TemplateRef, ViewContainerRef, effect, inject } from '@angular/core'; import { ViewModeService } from '../../core/services/view-mode.service'; /** @@ -14,11 +14,11 @@ import { ViewModeService } from '../../core/services/view-mode.service'; standalone: true }) export class AuditorOnlyDirective { - constructor( - private templateRef: TemplateRef, - private viewContainer: ViewContainerRef, - private viewModeService: ViewModeService - ) { + private readonly templateRef = inject(TemplateRef); + private readonly viewContainer = inject(ViewContainerRef); + private readonly viewModeService = inject(ViewModeService); + + constructor() { effect(() => { if (this.viewModeService.isAuditor()) { this.viewContainer.createEmbeddedView(this.templateRef); diff --git a/src/Web/StellaOps.Web/src/app/shared/directives/operator-only.directive.ts b/src/Web/StellaOps.Web/src/app/shared/directives/operator-only.directive.ts index 025117b11..5820e3e91 100644 --- a/src/Web/StellaOps.Web/src/app/shared/directives/operator-only.directive.ts +++ b/src/Web/StellaOps.Web/src/app/shared/directives/operator-only.directive.ts @@ -1,4 +1,4 @@ -import { Directive, TemplateRef, ViewContainerRef, effect } from '@angular/core'; +import { Directive, TemplateRef, ViewContainerRef, effect, inject } from '@angular/core'; import { ViewModeService } from '../../core/services/view-mode.service'; /** @@ -14,11 +14,11 @@ import { ViewModeService } from '../../core/services/view-mode.service'; standalone: true }) export class OperatorOnlyDirective { - constructor( - private templateRef: TemplateRef, - private viewContainer: ViewContainerRef, - private viewModeService: ViewModeService - ) { + private readonly templateRef = inject(TemplateRef); + private readonly viewContainer = inject(ViewContainerRef); + private readonly viewModeService = inject(ViewModeService); + + constructor() { effect(() => { if (this.viewModeService.isOperator()) { this.viewContainer.createEmbeddedView(this.templateRef);