From 3ecafc49a3805e5aa7fc89507a389359baa0db88 Mon Sep 17 00:00:00 2001 From: master <> Date: Mon, 9 Mar 2026 22:11:08 +0200 Subject: [PATCH] Preserve live scope across evidence and registry flows --- ...15_FE_live_scope_preservation_followups.md | 43 +++++++++++++++++++ .../live-frontdoor-changed-surfaces.mjs | 4 ++ .../evidence-thread-view.component.spec.ts | 1 + .../evidence-thread-view.component.ts | 3 +- .../registry-admin.component.spec.ts | 40 +++++++++++++++++ .../registry-admin.component.ts | 2 + .../StellaOps.Web/tsconfig.spec.features.json | 12 ++++++ 7 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 docs/implplan/SPRINT_20260309_015_FE_live_scope_preservation_followups.md create mode 100644 src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.spec.ts create mode 100644 src/Web/StellaOps.Web/tsconfig.spec.features.json diff --git a/docs/implplan/SPRINT_20260309_015_FE_live_scope_preservation_followups.md b/docs/implplan/SPRINT_20260309_015_FE_live_scope_preservation_followups.md new file mode 100644 index 000000000..97e1701c7 --- /dev/null +++ b/docs/implplan/SPRINT_20260309_015_FE_live_scope_preservation_followups.md @@ -0,0 +1,43 @@ +# Sprint 20260309_015 - Live Scope Preservation Follow-ups + +## Topic & Scope +- Repair user-visible scope loss uncovered by live Playwright after the runtime-fault rebuild. +- Keep tenant and region context stable when users move between evidence-thread and integration-admin actions. +- Working directory: `src/Web/StellaOps.Web/**`. +- Expected evidence: focused Angular specs, rebuilt web bundle, live Playwright changed-surfaces recheck. + +## Dependencies & Concurrency +- Depends on `SPRINT_20260309_014_Platform_live_runtime_fault_repair.md` because the backend/runtime repair must be stable before UI scope regressions are meaningful. +- Safe parallelism: avoid unrelated search and shell work already in flight; stage only evidence-thread, registry-admin, Playwright harness, and sprint-doc files. + +## Documentation Prerequisites +- `docs/modules/platform/architecture-overview.md` +- `docs/code-of-conduct/CODE_OF_CONDUCT.md` +- `docs/code-of-conduct/TESTING_PRACTICES.md` + +## Delivery Tracker + +### TASK-015-001 - Preserve scope on changed-surface action flows +Status: DONE +Dependency: none +Owners: QA, Developer +Task description: +- Fix changed-surface actions that drop tenant and region query scope during navigation, then tighten the live Playwright harness so the same regressions fail immediately. + +Completion criteria: +- [x] Evidence thread "Back to Search" keeps active scope query params. +- [x] Registry admin tab navigation keeps active scope query params. +- [x] Focused Angular specs and live Playwright changed-surfaces verification pass. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-09 | Sprint created after live changed-surfaces Playwright reported scoped actions navigating without tenant/region query preservation. | Codex | +| 2026-03-09 | Root causes confirmed in `EvidenceThreadViewComponent.onBack()` and registry-admin tab links. Added focused feature-spec coverage, rebuilt `dist/stellaops-web/browser`, synced the live `compose_console-dist` volume, and re-ran `live-frontdoor-changed-surfaces.mjs`; Playwright now records tenant/region-preserving URLs for `back-to-search` and `audit-tab`. | Codex | + +## Decisions & Risks +- This sprint treats scope-preservation regressions as product defects even when the destination page still renders, because silent context loss breaks reproducibility and link sharing. +- Feature specs remain excluded from the default Angular test target to keep routine unit runs lightweight; targeted UI feature coverage for this slice is registered in `tsconfig.spec.features.json` and executed explicitly. + +## Next Checkpoints +- Next defect cluster from the same live Playwright sweep: release-investigation `deploy-diff` still lands in a `Missing Parameters` state, and `change-trace` still renders with `No Change Trace Loaded`. diff --git a/src/Web/StellaOps.Web/scripts/live-frontdoor-changed-surfaces.mjs b/src/Web/StellaOps.Web/scripts/live-frontdoor-changed-surfaces.mjs index 7e1b16656..b9fe4e76f 100644 --- a/src/Web/StellaOps.Web/scripts/live-frontdoor-changed-surfaces.mjs +++ b/src/Web/StellaOps.Web/scripts/live-frontdoor-changed-surfaces.mjs @@ -83,6 +83,7 @@ const surfaceConfigs = [ selector: 'main button:has-text("Back to Search")', expectedUrlPattern: '/evidence/threads', expectedTextPattern: /evidence threads/i, + requiredUrlFragments: ['tenant=', 'regions='], }, ], }, @@ -115,6 +116,7 @@ const surfaceConfigs = [ selector: 'a[href*="/registry-admin/audit"], button:has-text("Audit")', expectedUrlPattern: '/registry-admin/audit', expectedTextPattern: /audit/i, + requiredUrlFragments: ['tenant=', 'regions='], }, ], }, @@ -389,7 +391,9 @@ async function verifySurfaceActions(context, surface) { const bodyText = await collectBodyText(page); const headingText = firstMatchingHeading(action.expectedTextPattern, headings); const finalUrl = page.url(); + const hasRequiredUrlFragments = (action.requiredUrlFragments ?? []).every((fragment) => finalUrl.includes(fragment)); const ok = finalUrl.includes(action.expectedUrlPattern) + && hasRequiredUrlFragments && matchesPattern(action.expectedTextPattern, headings, bodyText); results.push({ diff --git a/src/Web/StellaOps.Web/src/app/features/evidence-thread/__tests__/evidence-thread-view.component.spec.ts b/src/Web/StellaOps.Web/src/app/features/evidence-thread/__tests__/evidence-thread-view.component.spec.ts index a690c05f7..8471a8d3b 100644 --- a/src/Web/StellaOps.Web/src/app/features/evidence-thread/__tests__/evidence-thread-view.component.spec.ts +++ b/src/Web/StellaOps.Web/src/app/features/evidence-thread/__tests__/evidence-thread-view.component.spec.ts @@ -89,6 +89,7 @@ describe('EvidenceThreadViewComponent', () => { expect(router.navigate).toHaveBeenCalledWith(['/evidence/threads'], { queryParams: { purl: 'pkg:oci/acme/api@sha256:abc123' }, + queryParamsHandling: 'merge', }); }); diff --git a/src/Web/StellaOps.Web/src/app/features/evidence-thread/components/evidence-thread-view/evidence-thread-view.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence-thread/components/evidence-thread-view/evidence-thread-view.component.ts index b54c2f54d..949767fc1 100644 --- a/src/Web/StellaOps.Web/src/app/features/evidence-thread/components/evidence-thread-view/evidence-thread-view.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/evidence-thread/components/evidence-thread-view/evidence-thread-view.component.ts @@ -87,7 +87,8 @@ export class EvidenceThreadViewComponent implements OnInit, OnDestroy { onBack(): void { const purl = this.returnPurl(); void this.router.navigate(['/evidence/threads'], { - queryParams: purl ? { purl } : {}, + queryParams: purl ? { purl } : undefined, + queryParamsHandling: 'merge', }); } diff --git a/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.spec.ts b/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.spec.ts new file mode 100644 index 000000000..62b2b8060 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.spec.ts @@ -0,0 +1,40 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterLink, provideRouter } from '@angular/router'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; + +import { REGISTRY_ADMIN_API } from '../../core/api/registry-admin.client'; +import { RegistryAdminComponent } from './registry-admin.component'; + +describe('RegistryAdminComponent', () => { + const registryAdminApiStub = { + listPlans: () => of([]), + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RegistryAdminComponent], + providers: [ + provideRouter([]), + ], + }) + .overrideComponent(RegistryAdminComponent, { + set: { + providers: [{ provide: REGISTRY_ADMIN_API, useValue: registryAdminApiStub }], + }, + }) + .compileComponents(); + }); + + it('merges the active query scope across registry admin tabs', () => { + const fixture = TestBed.createComponent(RegistryAdminComponent); + fixture.detectChanges(); + + const links = fixture.debugElement + .queryAll(By.directive(RouterLink)) + .map((debugElement) => debugElement.injector.get(RouterLink)); + + expect(links.length).toBe(2); + expect(links.every((link) => link.queryParamsHandling === 'merge')).toBeTrue(); + }); +}); diff --git a/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.ts b/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.ts index fbc029a88..5b4ac4620 100644 --- a/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/registry-admin/registry-admin.component.ts @@ -49,6 +49,7 @@ type TabType = 'plans' | 'audit'; class="registry-admin__tab" [class.registry-admin__tab--active]="activeTab() === 'plans'" routerLink="plans" + queryParamsHandling="merge" role="tab" [attr.aria-selected]="activeTab() === 'plans'" > @@ -58,6 +59,7 @@ type TabType = 'plans' | 'audit'; class="registry-admin__tab" [class.registry-admin__tab--active]="activeTab() === 'audit'" routerLink="audit" + queryParamsHandling="merge" role="tab" [attr.aria-selected]="activeTab() === 'audit'" > diff --git a/src/Web/StellaOps.Web/tsconfig.spec.features.json b/src/Web/StellaOps.Web/tsconfig.spec.features.json new file mode 100644 index 000000000..a4db98a64 --- /dev/null +++ b/src/Web/StellaOps.Web/tsconfig.spec.features.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.spec.json", + "include": [], + "files": [ + "src/test-setup.ts", + "src/app/core/api/first-signal.client.spec.ts", + "src/app/core/console/console-status.service.spec.ts", + "src/app/features/policy-simulation/simulation-dashboard.component.spec.ts", + "src/app/features/registry-admin/registry-admin.component.spec.ts", + "src/app/features/evidence-thread/__tests__/evidence-thread-view.component.spec.ts" + ] +}