diff --git a/docs/features/checked/web/orphan-finding-list-consolidation.md b/docs/features/checked/web/orphan-finding-list-consolidation.md
new file mode 100644
index 000000000..113d68836
--- /dev/null
+++ b/docs/features/checked/web/orphan-finding-list-consolidation.md
@@ -0,0 +1,53 @@
+# Orphan Finding List Consolidation
+
+Sprint: SPRINT_20260308_020_FE_orphan_finding_list_consolidation
+
+## Summary
+
+Revived the dormant shared `FindingListComponent` and `FindingRowComponent` by adopting them on two mounted surfaces that previously used bespoke finding list rendering:
+
+1. **FindingsContainerComponent** (`features/findings/container/`): Replaced the bespoke `FindingsListComponent` (`app-findings-list`) with the shared `FindingListComponent` (`stella-finding-list`). Added a `findingEvidenceItems` computed signal that adapts `Finding[]` to `FindingEvidenceResponse[]` with severity-to-risk-score mapping and VEX status bridging.
+
+2. **ReleaseDetailComponent** (`features/release-orchestrator/releases/release-detail/`): Replaced the bespoke inline HTML `
` on the security-inputs tab with the shared `FindingListComponent`. Added a `securityFindingEvidenceItems` computed signal that adapts `SecurityFindingProjection[]` to `FindingEvidenceResponse[]` with reachability path mapping and VEX status forwarding.
+
+## Mounted hosts
+
+| Widget | Host component | Route context | Adoption type |
+|---|---|---|---|
+| FindingListComponent + FindingRowComponent | FindingsContainerComponent | `/security/findings` (detail view) | Replaces bespoke `app-findings-list` |
+| FindingListComponent + FindingRowComponent | ReleaseDetailComponent | `/releases/:releaseId` (security-inputs tab) | Replaces bespoke inline `
` |
+
+## Exclusions
+
+| Surface | Reason |
+|---|---|
+| `FindingsDetailPageComponent` (`features/triage/components/findings-detail-page/`) | Card-based layout with triage lane toggle, gated buckets, and gating reason filter. Interaction model is materially different from the shared tabular list. |
+| `TriageWorkspaceComponent` (`features/triage/`) | Uses `FindingCardModel` (Vulnerability + AffectedComponent) with deeply integrated keyboard navigation, VEX decision modals, AI recommendations, reachability drawers, and bulk VEX. Interaction model is materially different. |
+| `VulnerabilityExplorerComponent` (`features/vulnerabilities/`) | Reserved for sprint 013. Explicitly excluded per sprint scope. |
+
+## Data contracts
+
+- `FindingEvidenceResponse` (from `triage-evidence.models.ts`): The shared list's primary input type.
+- `Finding` (from `findings-list.component.ts`): Bespoke model used by FindingsContainerComponent; adapted via `mapFindingToEvidence()`.
+- `SecurityFindingProjection` (inline in `release-detail.component.ts`): Bespoke model used by ReleaseDetailComponent; adapted via `mapSecurityFindingToEvidence()`.
+
+## Adapter strategy
+
+Both host components use computed signals that transform their existing data models into `FindingEvidenceResponse[]`:
+
+- Severity strings map to numeric risk scores (critical=90, high=70, medium=45, low=20).
+- Finding status maps to VEX evidence where semantically appropriate (fixed -> `{status: 'fixed'}`, excepted -> `{status: 'not_affected'}`).
+- Reachability booleans map to `reachable_path` arrays.
+- Component metadata is projected into `ComponentRef` with generic PURL construction.
+
+## Test coverage
+
+- `findings-container-finding-list-adoption.component.spec.ts`: Verifies findingEvidenceItems derivation, field mapping (finding_id, cve, component, risk_score, VEX status), template rendering of shared list element, and absence of bespoke element.
+- `release-detail-finding-list-adoption.component.spec.ts`: Verifies securityFindingEvidenceItems derivation, field mapping (finding_id, cve, component, risk_score, reachable_path, VEX status), and empty-findings handling.
+
+## Files changed
+
+- `src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts`
+- `src/Web/StellaOps.Web/src/app/features/release-orchestrator/releases/release-detail/release-detail.component.ts`
+- `src/Web/StellaOps.Web/src/app/features/findings/container/findings-container-finding-list-adoption.component.spec.ts` (new)
+- `src/Web/StellaOps.Web/src/app/features/release-orchestrator/releases/release-detail/release-detail-finding-list-adoption.component.spec.ts` (new)
diff --git a/docs/implplan/SPRINT_20260308_020_FE_orphan_finding_list_consolidation.md b/docs/implplan/SPRINT_20260308_020_FE_orphan_finding_list_consolidation.md
new file mode 100644
index 000000000..e46e5a406
--- /dev/null
+++ b/docs/implplan/SPRINT_20260308_020_FE_orphan_finding_list_consolidation.md
@@ -0,0 +1,96 @@
+# Sprint 20260308-020 - FE Orphan Finding List Consolidation
+
+## Topic & Scope
+- Revive `FindingListComponent` and `FindingRowComponent` by adopting them on mounted findings, triage, and release-review surfaces that still maintain separate bespoke lists.
+- Use this sprint to consolidate the shared finding-list family, not to redesign every findings workflow.
+- Explicit non-goals: do not touch vulnerability-explorer consumers reserved for sprint `013`, and do not absorb unrelated filter-toolbar work reserved for sprint `015`.
+- Working directory: `src/Web/StellaOps.Web`.
+- Allowed coordination edits: `docs/modules/ui/orphan-revival-batch/README.md`, `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, `docs/features/checked/web/`, `src/Web/StellaOps.Web/src/app/shared/components/finding-list.component.ts`, `src/Web/StellaOps.Web/src/app/shared/components/finding-row.component.ts`, `src/Web/StellaOps.Web/src/app/features/findings/`, `src/Web/StellaOps.Web/src/app/features/triage/`, and `src/Web/StellaOps.Web/src/app/features/release-orchestrator/`.
+- Expected evidence: focused Angular tests, one checked-feature note, and sprint execution-log updates.
+
+## Dependencies & Concurrency
+- Hard dependency inside the orphan revival batch: none.
+- External prerequisite already satisfied: findings, triage, and release-review shells are already mounted.
+- Safe parallelism:
+ - Can run in parallel with sprint `013` because vulnerability-explorer consumers are excluded.
+ - Can run in parallel with route-reconnection sprints because this sprint does not own router parent files.
+ - This sprint exclusively owns `finding-list` and `finding-row` while staffed.
+
+## Documentation Prerequisites
+- `docs/modules/ui/orphan-revival-batch/README.md`
+- `src/Web/StellaOps.Web/AGENTS.md`
+- `src/Web/StellaOps.Web/src/app/shared/components/finding-list.component.ts`
+- `src/Web/StellaOps.Web/src/app/shared/components/finding-row.component.ts`
+- `src/Web/StellaOps.Web/src/app/features/findings/findings-list.component.ts`
+- `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.ts`
+
+## Delivery Tracker
+
+### FE-OFL-001 - Freeze mounted list-host matrix
+Status: DONE
+Dependency: none
+Owners: Developer (FE), Project Manager
+Task description:
+- Freeze the mounted findings, triage, and release-review hosts that will adopt the shared finding-list family.
+- Record any list host that should stay bespoke because its interaction model is materially different.
+
+Completion criteria:
+- [x] Mounted host matrix is recorded in the execution log.
+- [x] Hosts reserved for other sprints are explicitly excluded.
+- [x] Compatibility notes for each adopted host are recorded.
+
+### FE-OFL-002 - Adopt shared finding list on canonical findings surfaces
+Status: DONE
+Dependency: FE-OFL-001
+Owners: Developer (FE)
+Task description:
+- Replace bespoke list rendering on the selected canonical findings surfaces with the shared `FindingListComponent` and `FindingRowComponent` where the interaction model aligns.
+
+Completion criteria:
+- [x] Selected findings surfaces render the shared list family.
+- [x] Sorting, expansion, and core status affordances remain usable.
+- [x] Any required host-level adapters stay bounded to the findings family.
+
+### FE-OFL-003 - Adopt shared finding list on triage and release-review surfaces
+Status: DONE
+Dependency: FE-OFL-001
+Owners: Developer (FE)
+Task description:
+- Extend the shared finding-list family to the frozen triage and release-review hosts where that consolidation improves consistency without flattening domain-specific actions.
+
+Completion criteria:
+- [x] Selected triage and release-review hosts render the shared list family.
+- [x] Domain-specific actions remain available in the adopted hosts.
+- [x] Hosts that do not fit the shared list are explicitly excluded with reasons.
+
+### FE-OFL-004 - Verify and document finding-list revival
+Status: DONE
+Dependency: FE-OFL-002
+Owners: Test Automation, Documentation author
+Task description:
+- Add focused Angular coverage for the shared finding-list adoption and document the shipped consolidation slice.
+
+Completion criteria:
+- [x] Angular tests cover the shared finding-list family in mounted consumers.
+- [x] Checked-feature note exists under `docs/features/checked/web/`.
+- [x] UI plan/task docs reflect the finding-list consolidation.
+
+## Execution Log
+| Date (UTC) | Update | Owner |
+| --- | --- | --- |
+| 2026-03-08 | Sprint created from the orphan-revival batch to revive the dormant shared finding-list family across mounted findings, triage, and release-review surfaces. | Project Manager |
+| 2026-03-08 | FE-OFL-001: Frozen mounted host matrix. Shared FindingListComponent/FindingRowComponent accept FindingEvidenceResponse[] from triage-evidence.models.ts. ADOPT: (1) FindingsContainerComponent at features/findings/container/ — currently uses bespoke FindingsListComponent (app-findings-list) with Finding[] interface; will map Finding to FindingEvidenceResponse via host adapter. (2) ReleaseDetailComponent at features/release-orchestrator/releases/release-detail/ — currently uses inline table with SecurityFindingProjection; will map SecurityFindingProjection to FindingEvidenceResponse via host adapter. EXCLUDED (bespoke stays): (3) FindingsDetailPageComponent at features/triage/components/findings-detail-page/ — card-based layout with triage lane toggle, gated buckets, gating reason filter; interaction model is materially different from shared tabular list. (4) TriageWorkspaceComponent at features/triage/ — uses FindingCardModel (Vulnerability + AffectedComponent) with deeply integrated keyboard navigation, VEX decision modals, AI recommendations, reachability drawers, bulk VEX; interaction model is materially different. (5) VulnerabilityExplorerComponent at features/vulnerabilities/ — reserved for sprint 013, explicitly excluded. | Developer (FE) |
+| 2026-03-08 | FE-OFL-002: Replaced bespoke FindingsListComponent (app-findings-list) usage in FindingsContainerComponent with shared FindingListComponent (stella-finding-list). Added findingEvidenceItems computed signal that maps Finding[] to FindingEvidenceResponse[] via mapFindingToEvidence() adapter. Severity maps to risk_score (critical=90, high=70, medium=45, low=20). Status maps to VEX status where applicable (fixed -> fixed, excepted -> not_affected). Sorting and expansion provided by shared component. | Developer (FE) |
+| 2026-03-08 | FE-OFL-003: Adopted shared FindingListComponent on ReleaseDetailComponent security-inputs tab. Replaced bespoke inline HTML table with stella-finding-list. Added securityFindingEvidenceItems computed signal that maps SecurityFindingProjection[] to FindingEvidenceResponse[] via mapSecurityFindingToEvidence() adapter. Reachability is mapped to reachable_path. VEX status is forwarded where not under_investigation. Added onSecurityFindingSelected handler that navigates to triage workspace with release context. Triage surfaces (FindingsDetailPageComponent, TriageWorkspaceComponent) excluded per host matrix — interaction models are materially different. | Developer (FE) |
+| 2026-03-08 | FE-OFL-004: Added focused Angular tests for both adoption hosts. Created findings-container-finding-list-adoption.component.spec.ts (11 tests) and release-detail-finding-list-adoption.component.spec.ts (10 tests). Created checked-feature note at docs/features/checked/web/orphan-finding-list-consolidation.md. All four tasks DONE. | Developer (FE) |
+
+## Decisions & Risks
+- Decision: this sprint consolidates the shared finding-list family across mounted shells instead of restoring any dead findings prototype wholesale.
+- Risk: some hosts may rely on bespoke actions or lane semantics that do not fit the shared list without awkward adapters.
+- Mitigation: freeze the host matrix first and explicitly record any host that should remain purpose-built.
+- Decision: FindingsDetailPageComponent and TriageWorkspaceComponent stay bespoke because their card-based layouts, lane filtering, gating reason display, keyboard triage shortcuts, and VEX decision modals are materially different from the shared tabular finding-list family. Adopting the shared list would require flattening these domain-specific interaction models.
+- Decision: FindingsContainerComponent and ReleaseDetailComponent adopt the shared list because their rendering is simple tabular display that aligns with the shared component's table-based layout. Host-level adapters will bridge their data contracts (Finding/SecurityFindingProjection) to FindingEvidenceResponse.
+
+## Next Checkpoints
+- 2026-03-09: mounted host matrix frozen.
+- 2026-03-11: consolidation criteria agreed.
diff --git a/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container-finding-list-adoption.component.spec.ts b/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container-finding-list-adoption.component.spec.ts
new file mode 100644
index 000000000..2e733e71b
--- /dev/null
+++ b/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container-finding-list-adoption.component.spec.ts
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: BUSL-1.1
+// Copyright (c) 2025 StellaOps
+// Sprint: SPRINT_20260308_020_FE_orphan_finding_list_consolidation
+// Task: FE-OFL-004 - Verify shared FindingListComponent adoption on FindingsContainerComponent
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { ActivatedRoute, convertToParamMap, provideRouter } from '@angular/router';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { of, BehaviorSubject } from 'rxjs';
+import { signal } from '@angular/core';
+
+import { FindingsContainerComponent } from './findings-container.component';
+import { ViewPreferenceService, FindingsViewMode } from '../../../core/services/view-preference.service';
+import { CompareService } from '../../compare/services/compare.service';
+import { SECURITY_FINDINGS_API } from '../../../core/api/security-findings.client';
+
+describe('FindingsContainerComponent (shared FindingList adoption)', () => {
+ let component: FindingsContainerComponent;
+ let fixture: ComponentFixture;
+ let queryParamMap$: BehaviorSubject;
+ let paramMap$: BehaviorSubject;
+
+ const mockFindings = [
+ {
+ id: 'finding-1',
+ advisoryId: 'CVE-2026-8001',
+ package: 'backend-api',
+ version: '2.5.0',
+ severity: 'CRITICAL',
+ vexStatus: 'affected',
+ delta: 'new',
+ firstSeen: '2026-02-10T09:30:00Z',
+ },
+ {
+ id: 'finding-2',
+ advisoryId: 'CVE-2026-8002',
+ package: 'frontend-lib',
+ version: '1.0.3',
+ severity: 'HIGH',
+ vexStatus: 'fixed',
+ delta: 'resolved',
+ firstSeen: '2026-02-11T10:00:00Z',
+ },
+ {
+ id: 'finding-3',
+ advisoryId: 'CVE-2026-8003',
+ package: 'crypto-util',
+ version: '3.1.0',
+ severity: 'LOW',
+ vexStatus: 'not_affected',
+ delta: 'carried',
+ firstSeen: '2026-02-12T08:15:00Z',
+ },
+ ];
+
+ beforeEach(async () => {
+ queryParamMap$ = new BehaviorSubject(convertToParamMap({ view: 'detail' }));
+ paramMap$ = new BehaviorSubject(convertToParamMap({ scanId: 'test-scan-123' }));
+
+ const mockViewPref = jasmine.createSpyObj('ViewPreferenceService', ['getViewMode', 'setViewMode'], {
+ viewMode: signal('detail').asReadonly(),
+ });
+ mockViewPref.getViewMode.and.returnValue('detail');
+
+ const mockCompare = jasmine.createSpyObj('CompareService', [
+ 'getBaselineRecommendations',
+ 'getBaselineRationale',
+ 'getTarget',
+ 'computeDelta',
+ ]);
+ mockCompare.getBaselineRecommendations.and.returnValue(of({
+ selectedDigest: null,
+ selectionReason: 'none',
+ alternatives: [],
+ autoSelectEnabled: true,
+ }));
+ mockCompare.computeDelta.and.returnValue(of({ categories: [], items: [] }));
+
+ const mockFindingsApi = jasmine.createSpyObj('SecurityFindingsApi', ['listFindings']);
+ mockFindingsApi.listFindings.and.returnValue(of(mockFindings));
+
+ await TestBed.configureTestingModule({
+ imports: [FindingsContainerComponent, NoopAnimationsModule, HttpClientTestingModule],
+ providers: [
+ provideRouter([]),
+ { provide: ViewPreferenceService, useValue: mockViewPref },
+ { provide: CompareService, useValue: mockCompare },
+ { provide: SECURITY_FINDINGS_API, useValue: mockFindingsApi },
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ paramMap: paramMap$,
+ queryParamMap: queryParamMap$,
+ snapshot: {
+ paramMap: convertToParamMap({ scanId: 'test-scan-123' }),
+ queryParamMap: convertToParamMap({ view: 'detail' }),
+ },
+ },
+ },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(FindingsContainerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should compute findingEvidenceItems from raw findings', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ expect(items.length).toBe(3);
+ });
+
+ it('should map finding_id from Finding.id', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ expect(items[0].finding_id).toBe('finding-1');
+ expect(items[1].finding_id).toBe('finding-2');
+ expect(items[2].finding_id).toBe('finding-3');
+ });
+
+ it('should map cve from Finding.advisoryId', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ expect(items[0].cve).toBe('CVE-2026-8001');
+ expect(items[1].cve).toBe('CVE-2026-8002');
+ });
+
+ it('should map component name and version', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ expect(items[0].component?.name).toBe('backend-api');
+ expect(items[0].component?.version).toBe('2.5.0');
+ expect(items[1].component?.name).toBe('frontend-lib');
+ expect(items[1].component?.version).toBe('1.0.3');
+ });
+
+ it('should map severity to risk_score', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ // critical -> 90
+ expect(items[0].score_explain?.risk_score).toBe(90);
+ // high -> 70
+ expect(items[1].score_explain?.risk_score).toBe(70);
+ // low -> 20
+ expect(items[2].score_explain?.risk_score).toBe(20);
+ });
+
+ it('should map fixed status to VEX fixed', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ // finding-2 has vexStatus 'fixed', delta 'resolved' -> status = 'fixed'
+ expect(items[1].vex?.status).toBe('fixed');
+ });
+
+ it('should map excepted status to VEX not_affected', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const items = component.findingEvidenceItems();
+ // finding-3 has vexStatus 'not_affected' -> status = 'excepted'
+ expect(items[2].vex?.status).toBe('not_affected');
+ });
+
+ it('should render shared finding list host element in detail view', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const el: HTMLElement = fixture.nativeElement;
+ const host = el.querySelector('[data-testid="shared-finding-list-host"]');
+ expect(host).toBeTruthy();
+ });
+
+ it('should render stella-finding-list element in detail view', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const el: HTMLElement = fixture.nativeElement;
+ const list = el.querySelector('stella-finding-list');
+ expect(list).toBeTruthy();
+ });
+
+ it('should not render bespoke app-findings-list element', async () => {
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const el: HTMLElement = fixture.nativeElement;
+ const bespoke = el.querySelector('app-findings-list');
+ expect(bespoke).toBeFalsy();
+ });
+});
diff --git a/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts b/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts
index b91bbca65..717a2a816 100644
--- a/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts
@@ -2,6 +2,8 @@
// findings-container.component.ts
// Sprint: SPRINT_1227_0005_0001_FE_diff_first_default
// Task: T3 — Container component for findings with view switching
+// Sprint: SPRINT_20260308_020_FE_orphan_finding_list_consolidation
+// Task: FE-OFL-002 — Adopt shared FindingListComponent on canonical findings surface
// -----------------------------------------------------------------------------
import { Component, ChangeDetectionStrategy, inject, signal, computed, OnInit } from '@angular/core';
@@ -18,7 +20,9 @@ import { forkJoin, of } from 'rxjs';
import { ViewPreferenceService, FindingsViewMode } from '../../../core/services/view-preference.service';
import { FindingsViewToggleComponent } from '../../../shared/components/findings-view-toggle/findings-view-toggle.component';
import { CompareViewComponent } from '../../compare/components/compare-view/compare-view.component';
-import { FindingsListComponent, Finding } from '../findings-list.component';
+import { FindingListComponent } from '../../../shared/components/finding-list.component';
+import type { FindingEvidenceResponse } from '../../../core/api/triage-evidence.models';
+import { type Finding } from '../findings-list.component';
import { CompareService } from '../../compare/services/compare.service';
import {
SECURITY_FINDINGS_API,
@@ -98,7 +102,7 @@ function mapFinding(source: DetailFindingSource): Finding {
MatProgressSpinnerModule,
FindingsViewToggleComponent,
CompareViewComponent,
- FindingsListComponent
+ FindingListComponent,
],
template: `
@@ -149,9 +153,11 @@ function mapFinding(source: DetailFindingSource): Finding {