diff --git a/docs/features/checked/web/settings-ia-rationalization-ui.md b/docs/features/checked/web/settings-ia-rationalization-ui.md new file mode 100644 index 000000000..a104e7da1 --- /dev/null +++ b/docs/features/checked/web/settings-ia-rationalization-ui.md @@ -0,0 +1,57 @@ +# Settings IA Rationalization + +## Summary +The Settings shell has been rationalized from a mixed bucket of user preferences, admin consoles, setup pages, and redirect shims into a truthful personal-preferences surface. The `/settings` default now lands on User Preferences (appearance, language, layout, AI assistant) instead of Integrations. + +## What changed + +### Settings default +- `/settings` now defaults to User Preferences instead of Integrations. + +### Personal preferences (canonical owner: Settings) +- `user-preferences` -- the single personal-settings page with Appearance, Language, Layout, and AI Assistant sections. + +### Merged preference leaves (redirects to user-preferences) +- `language` -- was a standalone duplicate of the language section already present in user-preferences. +- `ai-preferences` -- already redirected to user-preferences (preserved). + +### Admin/tenant leaves rehomed via redirects +| Legacy URL | Redirect Target | +|---|---| +| `/settings/admin` | `/administration/admin` | +| `/settings/admin/:page` | `/administration/admin/:page` | +| `/settings/branding` | `/console/admin/branding` | +| `/settings/identity-providers` | `/administration/identity-providers` | +| `/settings/system` | `/administration/system` | +| `/settings/security-data` | `/administration/security-data` | +| `/settings/offline` | `/administration/offline` | + +### Operations/setup leaves rehomed via redirects +| Legacy URL | Redirect Target | +|---|---| +| `/settings/integrations` | `/setup/integrations` | +| `/settings/integrations/:id` | `/setup/integrations/:id` | +| `/settings/usage` | `/setup/usage` | +| `/settings/notifications` | `/setup/notifications` | +| `/settings/policy` | `/ops/policy/governance` | +| `/settings/release-control` | `/setup/topology/environments` | +| `/settings/configuration-pane` | `/ops/platform-setup` | + +### Trust redirects preserved +All `trust/*` and `trust-signing/*` redirects to `/setup/trust-signing/*` remain unchanged. + +### Navigation config +- `identity-providers` admin nav item now points to `/administration/identity-providers` instead of `/settings/identity-providers`. + +### Administration routes +- `/administration/identity-providers` now loads the IdentityProvidersSettingsPageComponent directly instead of redirecting to `/settings/identity-providers` (breaks the redirect loop created by the settings rehoming). + +## Test evidence +- 22 new tests in `settings-ia-rationalization.spec.ts` covering personal preference defaults, merged redirects, admin redirects, ops redirects, trust preservation, and route count validation. +- 3 existing tests in `unified-settings-page.behavior.spec.ts` updated and passing. +- 5 existing tests in `setup-topology-trust-cutover.spec.ts` verified passing (no regression). +- All 30 settings tests pass, all 5 trust cutover tests pass. +- Build clean (no TypeScript errors). + +## Sprint +`SPRINT_20260308_026_FE_settings_information_architecture_rationalization` diff --git a/docs/implplan/SPRINT_20260308_025_FE_safe_cleanup_and_generated_artifacts_prune.md b/docs/implplan/SPRINT_20260308_025_FE_safe_cleanup_and_generated_artifacts_prune.md new file mode 100644 index 000000000..953f5631c --- /dev/null +++ b/docs/implplan/SPRINT_20260308_025_FE_safe_cleanup_and_generated_artifacts_prune.md @@ -0,0 +1,95 @@ +# Sprint 20260308_025 - FE Safe Cleanup And Generated Artifacts Prune + +## Topic & Scope +- Remove the approved generated and debug artifacts committed under the Web workspace so audits and review diffs stop mixing product code with disposable output. +- Remove only the confirmed orphan route file and the legacy `release-control` leaves that are no longer mounted anywhere except redirect shims. +- Keep live surfaces untouched, especially the mounted workflow replay, watchlist, witness, policy, triage, and hotfix flows. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/implplan/SPRINT_20260308_025_FE_safe_cleanup_and_generated_artifacts_prune.md`, `docs/modules/ui/TASKS.md`, and `docs/modules/ui/implementation_plan.md`. +- Expected evidence: clean git deletion set, successful Web build, successful Web test run, and execution-log updates. + +## Dependencies & Concurrency +- Depends on the preservation review already completed for the current route tree and shared-component inventory. +- Must not overlap with unrelated user changes in the existing `main` worktree, so execution happens from an isolated cleanup branch/worktree. +- Safe parallelism: documentation-only planning work for future settings or UX derivation sprints may proceed in parallel, but no other task should edit the same legacy `release-control` files during this sprint. + +## Documentation Prerequisites +- `AGENTS.md` +- `docs/modules/ui/AGENTS.md` +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/app.routes.ts` +- `src/Web/StellaOps.Web/src/app/routes/releases.routes.ts` + +## Delivery Tracker + +### FE-CLN-001 - Freeze the deletion allowlist +Status: DONE +Dependency: none +Owners: Developer (FE), Project Manager +Task description: +- Freeze the exact list of generated artifacts and orphan code files that are approved for deletion so the cleanup stays narrow and reviewable. +- Reconfirm that `hotfixes-queue.component.ts` and other mounted flows remain outside the delete set even though they live near the legacy tree. + +Completion criteria: +- [x] The deletion list is explicitly recorded in the sprint execution log. +- [x] Mounted or reused components are excluded from the cleanup. +- [x] The cleanup scope stays inside `src/Web/StellaOps.Web`. + +### FE-CLN-002 - Remove committed generated and debug artifacts +Status: DONE +Dependency: FE-CLN-001 +Owners: Developer (FE) +Task description: +- Delete the committed Storybook static bundle, Playwright HTML/debug outputs, manual screenshot folders, and ad hoc debug scripts/images that do not belong in source control. +- Keep any active test or runtime sources intact; only disposable generated or debugging assets belong in this task. + +Completion criteria: +- [x] The approved generated/debug artifact paths are removed from source control. +- [x] No product source file is changed as part of this deletion step. +- [x] Git diff shows only the intended artifact removals. + +### FE-CLN-003 - Remove the orphan route file and dead legacy release-control leaves +Status: DONE +Dependency: FE-CLN-001 +Owners: Developer (FE) +Task description: +- Delete the unused `workflow-visualization.routes.ts` file and the approved legacy `release-control` governance, regions, and setup leaves that are no longer mounted by the live route tree. +- Preserve the still-mounted hotfix queue and any live route imports under Releases. + +Completion criteria: +- [x] The orphan workflow-visualization route file is removed. +- [x] The approved legacy `release-control` governance, regions, and setup files are removed. +- [x] `hotfixes-queue.component.ts` remains in place and the build graph stays valid. + +### FE-CLN-004 - Rebuild, retest, and document the cleanup +Status: BLOCKED +Dependency: FE-CLN-002 +Owners: Developer (FE), Test Automation +Task description: +- Rebuild the Angular workspace and run the Web test suite after the deletions to prove the cleanup did not break route ownership or compile-time imports. +- Record the commands and results in the execution log, then mark the sprint complete. + +Completion criteria: +- [x] `npm run build` succeeds in `src/Web/StellaOps.Web`. +- [ ] `npm run test -- --watch=false` succeeds in `src/Web/StellaOps.Web`. +- [x] Sprint execution log captures the verification commands and outcomes. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to prune approved generated/debug artifacts plus confirmed orphan route and legacy release-control leaves from the Web workspace. | Codex | +| 2026-03-08 | FE-CLN-001: deletion allowlist frozen. Generated/debug removals: `storybook-static/**`, `playwright-report/index.html`, tracked `output/playwright/*` repro files, `qa-sidebar-manual-screens/**`, `scheduler-debug.png`, and `tmp-debug-*.js`. Code removals: `workflow-visualization.routes.ts`, legacy `release-control/governance/*`, `release-control/regions/*`, `release-control/setup/*`. Explicitly preserved: mounted `hotfixes-queue.component.ts` and live workflow replay components. | Codex | +| 2026-03-08 | FE-CLN-002 and FE-CLN-003 completed from isolated branch/worktree. One stale dead-code test file, `src/Web/StellaOps.Web/src/tests/release-control/release-control-setup.component.spec.ts`, was removed because it only imported the deleted legacy setup components. | Codex | +| 2026-03-08 | FE-CLN-004: `npm ci --prefer-offline --no-audit --no-fund` succeeded. `npm run build` succeeded with existing bundle-budget warnings only. `npm run test -- --watch=false` did not complete cleanly: after the dead setup spec was removed, the suite still hit unrelated existing assertion failures across multiple areas and eventually exhausted Node heap. A bounded `ng test --watch=false --include src/tests/platform/platform-setup-routes.spec.ts --include src/tests/release-control/release-control-structure.component.spec.ts` run confirmed platform-setup route coverage passes, while `release-control-structure.component.spec.ts` still has one existing assertion mismatch in the create-promotion review copy. | Codex | + +## Decisions & Risks +- Decision: use an isolated cleanup branch/worktree because the user-facing `main` checkout already contains unrelated modifications. +- Risk: stale preservation-map output could tempt broader deletion than the route tree supports. +- Mitigation: delete only files with explicit route/import confirmation, and keep mounted hotfix/workflow replay surfaces out of scope. +- Verification blocker: the broader Web test suite is not clean after the cleanup-specific dead setup spec is removed; failures span unrelated areas such as navigation, triage, i18n, topbar locale behavior, audit-bundle auth expectations, and stale release-control copy assertions, then the run exhausts Node heap. +- Mitigation: commit the scoped cleanup with truthful sprint status and leave the suite-wide failures to dedicated stabilization work instead of masking them as cleanup regressions. + +## Next Checkpoints +- Freeze the deletion allowlist and execute the cleanup. +- Rebuild and retest the Web workspace. +- Fast-forward `main` to the cleanup commit after verification succeeds. diff --git a/docs/implplan/SPRINT_20260308_026_FE_settings_information_architecture_rationalization.md b/docs/implplan/SPRINT_20260308_026_FE_settings_information_architecture_rationalization.md new file mode 100644 index 000000000..e9e097841 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_026_FE_settings_information_architecture_rationalization.md @@ -0,0 +1,145 @@ +# Sprint 20260308_026 - FE Settings Information Architecture Rationalization + +## Topic & Scope +- Rationalize the current `/settings/*` tree so it becomes a truthful personal-settings surface instead of a mixed bucket of user preferences, admin consoles, setup pages, and redirect shims. +- Preserve backward compatibility for existing links through explicit redirects where needed, but move ownership and discoverability back to the correct shells. +- Treat this as a UX-first IA rewrite with detailed implementation sequencing, not as a shallow route rename. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, `src/Web/StellaOps.Web/src/app/features/settings/**`, `src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts`, relevant canonical owner routes under `src/Web/StellaOps.Web/src/app/routes/**`, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: route inventory, IA contract, Angular route/nav tests, UX verification notes, and execution-log updates. + +## Dependencies & Concurrency +- Depends on the current route inventory and the review that classified settings leaves into personal, admin/setup, and alias buckets. +- Should not run in parallel with other routing rewrites that touch `settings.routes.ts`, user-menu navigation, or canonical Setup/Admin ownership paths. +- Safe parallelism: pure shared-component derivation sprints can proceed in parallel if they do not edit settings routes or settings host templates. + +## Documentation Prerequisites +- `AGENTS.md` +- `docs/modules/ui/AGENTS.md` +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/AGENTS.md` +- `src/Web/StellaOps.Web/src/app/features/settings/settings.routes.ts` +- `src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts` + +## Delivery Tracker + +### FE-SETIA-001 - Audit and classify every settings route +Status: DONE +Dependency: none +Owners: Product Manager, Developer (FE) +Task description: +- Produce the source-of-truth route inventory for every child of `/settings`, classifying each leaf as one of: personal preference, admin or tenant configuration, canonical-owner alias, or dead wrapper. +- Capture whether each leaf is already visible somewhere else in the product, whether it overlaps an existing page, and whether its current label truthfully matches what the page actually does. + +Completion criteria: +- [x] Every mounted `/settings/*` route is classified into a single ownership bucket. +- [x] Existing visible entry points outside Settings are identified for admin/setup leaves. +- [x] Duplicate or misleading leaves are called out explicitly before implementation begins. + +### FE-SETIA-002 - Freeze the target IA and backward-compatibility contract +Status: DONE +Dependency: FE-SETIA-001 +Owners: Product Manager, UX +Task description: +- Define the target contract for Settings so the shell only owns true personal preferences, while admin, tenant, policy, trust, and operations configuration live under their canonical Setup, Ops, or Console Admin owners. +- Decide which current URLs remain as redirects, which URLs are removed entirely, and which labels need to change for operator clarity. + +Completion criteria: +- [x] A final ownership decision exists for each current settings leaf. +- [x] Redirect-vs-removal behavior is defined for every legacy or misleading route. +- [x] The target IA is concise enough to explain in one operator-facing diagram or note. + +### FE-SETIA-003 - Build the personal-settings shell and navigation model +Status: DONE +Dependency: FE-SETIA-002 +Owners: UX, Developer (FE) +Task description: +- Redesign the Settings shell around personal preferences only, with explicit sections such as Appearance, Language, Assistant, and Navigation/Layout. +- Replace the current "global sidebar owns navigation" fiction with either an in-page settings nav or a sectioned preferences page that is visibly self-contained and understandable. + +Completion criteria: +- [x] The Settings shell has a truthful navigation model for personal preferences. +- [x] The shell works on desktop and mobile without relying on hidden URL-only leaves. +- [x] User-menu entry points land in a settings experience that is obviously personal, not administrative. + +### FE-SETIA-004 - Merge overlapping personal preference leaves +Status: DONE +Dependency: FE-SETIA-003 +Owners: Developer (FE), UX +Task description: +- Consolidate `language` and any other overlapping preference leaves into the primary User Preferences experience so personal settings are not split across near-duplicate pages. +- Preserve deep-link compatibility with redirects or anchored sections where helpful, but remove duplicate editing surfaces. + +Completion criteria: +- [x] Language preferences are owned by the personal settings experience instead of a duplicate page. +- [x] Duplicate personal-preference pages are removed or converted into thin redirects. +- [x] Preference-saving behavior remains intact after the merge. + +### FE-SETIA-005 - Rehome admin, tenant, and operations configuration leaves +Status: DONE +Dependency: FE-SETIA-002 +Owners: Developer (FE), Product Manager +Task description: +- Move or redirect `integrations`, `admin`, `branding`, `notifications`, `usage`, `system`, `security-data`, `identity-providers`, `policy`, `offline`, and related leaves to their correct canonical owners. +- Ensure these pages are discoverable from the correct Setup/Ops/Admin entry points instead of surviving only as hidden Settings URLs. + +Completion criteria: +- [x] Admin/setup leaves no longer present themselves as user settings. +- [x] Canonical owner routes expose visible entry points for the rehomed capabilities. +- [x] Legacy `/settings/*` bookmarks still resolve through controlled redirects where required. + +### FE-SETIA-006 - Remove or collapse wrapper and alias-only settings pages +Status: DONE +Dependency: FE-SETIA-005 +Owners: Developer (FE) +Task description: +- Delete or collapse any settings pages that only exist as wrapper launchpads into other shells and do not provide independent value. +- Keep the compatibility surface focused on redirects, not on maintaining duplicate shells with duplicated copy. + +Completion criteria: +- [x] Alias-only settings pages are reduced to redirects or removed. +- [x] No standalone wrapper remains if its only action is to link elsewhere. +- [x] Route ownership becomes obvious from the code tree. + +### FE-SETIA-007 - Add focused route, nav, and UX regression coverage +Status: DONE +Dependency: FE-SETIA-004 +Owners: Test Automation, Developer (FE) +Task description: +- Add regression coverage for the new Settings IA, including user-menu entry, redirected legacy URLs, and canonical owner entry points for rehomed admin/setup pages. +- Include tests that prove hidden pages are now either visible from the right place or intentionally redirected. + +Completion criteria: +- [x] Angular route/nav tests cover the new personal settings shell and key redirects. +- [x] Regression coverage exists for at least the current user-menu entry plus representative admin/setup redirects. +- [x] Known IA edge cases are documented in the sprint log or feature note. + +### FE-SETIA-008 - Sync docs and ship the IA decision +Status: DONE +Dependency: FE-SETIA-007 +Owners: Documentation author, Project Manager +Task description: +- Record the final Settings IA contract in the UI docs, update the UI task board and implementation plan, and add a checked-feature note once the implementation ships. +- Ensure future dead-code or preservation reviews have a truthful owner map for Settings. + +Completion criteria: +- [x] UI docs reflect the final Settings ownership model. +- [x] UI task/plan docs reference the shipped IA. +- [x] A checked-feature note exists for the implemented settings rationalization. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to rationalize Settings into a truthful personal-preferences surface and rehome admin/setup leaves to their canonical owners. | Codex | +| 2026-03-08 | All tasks DONE. Audited 20 settings child routes and classified into 3 personal-preference, 11 admin/tenant-config, and 6 ops/wrapper buckets. Settings default changed from Integrations to User Preferences. 14 admin/ops leaves converted to redirects pointing at their canonical owners (administration, setup, ops). Language merged into user-preferences via redirect. Identity-providers rehomed from settings to administration as canonical owner. Navigation config updated. 22 new route tests added. All 35 settings+trust tests pass. Build clean. | Developer (FE) | + +## Decisions & Risks +- Current risk: the existing Settings shell mixes user preferences with admin/setup pages, making most leaves either URL-only or misleadingly named. +- UX principle: Settings must answer "what can I personalize for myself?" while Setup/Admin answer "what do I configure for the installation or tenant?" +- Compatibility risk: old bookmarks may point to `/settings/*` admin leaves; mitigate with explicit redirects and route tests instead of duplicate shells. +- Decision: `/administration/identity-providers` now loads the component directly instead of redirecting back to `/settings/identity-providers`, breaking the redirect loop. +- Decision: Settings default route changed from Integrations to User Preferences, which is the correct personal-settings landing page. +- Decision: `release-control` and `configuration-pane` wrapper pages converted to redirects to their canonical setup/ops owners since they only linked elsewhere. + +## Next Checkpoints +- Archived. All tasks shipped. diff --git a/docs/implplan/SPRINT_20260308_027_FE_page_header_context_header_derivation.md b/docs/implplan/SPRINT_20260308_027_FE_page_header_context_header_derivation.md new file mode 100644 index 000000000..32663f570 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_027_FE_page_header_context_header_derivation.md @@ -0,0 +1,95 @@ +# Sprint 20260308_027 - FE Page Header To Context Header Derivation + +## Topic & Scope +- Replace the unused generic `PageHeaderComponent` with a stronger canonical header pattern derived from the already-mounted `ContextHeaderComponent`. +- Improve operator UX by standardizing title, eyebrow, chips, return action, contextual note, and header actions across admin and setup surfaces. +- Keep this sprint focused on header semantics, layout, and adoption, not on broader page redesign. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, `src/Web/StellaOps.Web/src/app/shared/ui/**`, target mounted pages that adopt the header, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: shared-header contract, focused component tests, adopted target pages, and docs updates. + +## Dependencies & Concurrency +- Depends on the current shared-UI inventory and the existence of mounted `ContextHeaderComponent` usage in Watchlist, Reachability, Workflow Replay, and Policy shells. +- Safe parallelism: may run in parallel with settings IA work if it avoids editing `settings.routes.ts`; coordinate carefully if Settings adopts the derived header. + +## Documentation Prerequisites +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/shared/ui/page-header/page-header.component.ts` +- `src/Web/StellaOps.Web/src/app/shared/ui/context-header/context-header.component.ts` + +## Delivery Tracker + +### FE-PHD-001 - Freeze the canonical header contract +Status: DONE +Dependency: none +Owners: UX, Developer (FE) +Task description: +- Compare `PageHeaderComponent` against the mounted `ContextHeaderComponent` and define the single canonical header contract the product should keep. +- Document which capabilities remain mandatory: contextual eyebrow, chips, back action, action slot strategy, supportive note, and responsive stacking behavior. + +Completion criteria: +- [x] A single canonical header API is defined. +- [x] Unused or redundant `PageHeaderComponent` behavior is either absorbed or rejected explicitly. +- [x] Header semantics are described in UX terms, not only implementation terms. + +### FE-PHD-002 - Derive the reusable header primitive +Status: DONE +Dependency: FE-PHD-001 +Owners: Developer (FE) +Task description: +- Extend or refine the canonical header primitive so it can serve the pages that previously would have used the generic page header without regressing the richer contextual flows. +- Keep the API small and expressive; avoid two near-identical shared header components. + +Completion criteria: +- [x] The canonical header primitive supports the required title, metadata, and action variants. +- [x] `PageHeaderComponent` is either removed or reduced to a compatibility wrapper with a clear migration path. +- [x] Header behavior remains responsive and accessible. + +### FE-PHD-003 - Adopt the derived header on target pages +Status: DONE +Dependency: FE-PHD-002 +Owners: Developer (FE), UX +Task description: +- Adopt the derived header on carefully chosen mounted surfaces that currently rely on ad hoc title/subtitle/action markup, prioritizing pages that need contextual clarity. +- Use adoption to prove the pattern works for both dense operator surfaces and simpler settings/admin pages. + +Completion criteria: +- [x] At least one simple settings/admin page and one richer operational page adopt the derived header pattern. +- [x] Repeated header markup is removed from adopted surfaces. +- [x] The adopted pages gain clearer context and action placement. + +### FE-PHD-004 - Verify, document, and retire the orphan path +Status: DONE +Dependency: FE-PHD-003 +Owners: Test Automation, Documentation author +Task description: +- Add focused tests for the canonical header behavior and record the derivation decision in UI docs so future reviews treat the old generic header as intentionally superseded. + +Completion criteria: +- [x] Component or host tests cover the canonical header behavior. +- [x] UI docs explain the header derivation and adoption targets. +- [x] The old orphan path is no longer ambiguous in the shared inventory. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to derive the unused generic page header into the mounted context-header pattern and adopt one canonical header primitive. | Codex | +| 2026-03-08 | FE-PHD-001: Frozen the canonical header contract. `ContextHeaderComponent` is the single canonical header. `PageHeaderComponent` had only title, subtitle, and action slots; all useful bits absorbed. Canonical API: title (required), eyebrow (optional), subtitle (optional), contextNote (optional), chips (optional status indicators), backLabel+backClick (optional return action), headingLevel (1/2/3 for semantic HTML), testId (optional), header-actions content projection slot. | Developer (FE) | +| 2026-03-08 | FE-PHD-002: Enhanced `ContextHeaderComponent` with configurable heading level (h1/h2/h3), testId, arrow in return button, ARIA labels on return button and chip list, JSDoc on all inputs. `PageHeaderComponent` reduced to deprecated compatibility wrapper delegating to `ContextHeaderComponent`. | Developer (FE) | +| 2026-03-08 | FE-PHD-003: Adopted canonical header on 4 target pages: `RegistryAdminComponent` (admin/setup page), `PackRegistryBrowserComponent` (operational page), `DeadLetterDashboardComponent` (operational page), `OfflineKitComponent` (operational page). Removed repeated ad-hoc header markup from all 4. Each page now has eyebrow breadcrumb, consistent subtitle, and projected actions via the shared header. | Developer (FE) | +| 2026-03-08 | FE-PHD-004: Added 15 focused component tests covering title rendering, eyebrow/subtitle display, chips with ARIA roles, back action behavior, action slot projection, heading level configurability (h1/h2/h3), testId attribute, and responsive layout structure. All 15 pass. Updated sprint and docs. Marked `PageHeaderComponent` as deprecated in the shared index. | Test Automation | +| 2026-03-08 | Post-integration hardening: widened `ContextHeaderComponent` action-slot projection to accept legacy `primary-actions` and `secondary-actions` selectors, and added a dedicated `PageHeaderComponent` compatibility spec so wrapper behavior is now explicitly verified instead of assumed. | Developer (FE) | + +## Decisions & Risks +- **Decision: Single canonical header.** `ContextHeaderComponent` is the sole canonical header primitive. `PageHeaderComponent` is deprecated to a thin compatibility wrapper. +- **Decision: Heading level configurability.** Added `headingLevel` input (1, 2, or 3) to support pages nested inside shells that already provide an h1. Default remains h1. +- **Decision: Back button arrow.** Added a left arrow indicator to the return button for improved affordance and accessibility. +- **Decision: testId support.** Added `testId` input that maps to `data-testid` on the header element for Playwright/test targeting. +- **Decision: Adopted pages.** Registry Admin (admin/setup), Pack Registry Browser (operational), Dead-Letter Dashboard (operational), Offline Kit (operational). These four prove the pattern works across both simple admin and richer operational surfaces. +- **Decision: Compatibility selectors remain supported.** `ContextHeaderComponent` now accepts `[header-actions]`, `[secondary-actions]`, and `[primary-actions]` in its projection slot so the deprecated wrapper continues to behave correctly during migration. +- Risk: overfitting the header API to too many page variants could make the primitive hard to use. +- Mitigation: validated the API on a bounded 4-page adoption set. Future rollout should proceed incrementally. + +## Next Checkpoints +- Broader rollout of canonical header to remaining pages with ad-hoc headers (not scoped to this sprint). +- Eventual removal of `PageHeaderComponent` once no references remain. diff --git a/docs/implplan/SPRINT_20260308_028_FE_metric_card_dashboard_card_derivation.md b/docs/implplan/SPRINT_20260308_028_FE_metric_card_dashboard_card_derivation.md new file mode 100644 index 000000000..134488214 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_028_FE_metric_card_dashboard_card_derivation.md @@ -0,0 +1,118 @@ +# Sprint 20260308_028 - FE Metric Card Dashboard Derivation + +## Topic & Scope +- Derive the unused `MetricCardComponent` into a truthful canonical KPI card pattern for mounted ops, admin, quota, and system dashboards. +- Improve UX by standardizing deltas, directional semantics, health coloring, and supporting context instead of leaving each dashboard to invent its own card shape. +- Keep scope to KPI card behavior and adoption, not entire dashboard rewrites. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, shared UI card primitives, selected dashboard hosts, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: canonical KPI card contract, bounded adoption set, focused tests, and docs updates. + +## Dependencies & Concurrency +- Depends on the mounted overview surfaces already present across Operations, Administration, Usage, System, and related overview pages. +- Safe parallelism: may run alongside settings IA work if adoptions do not edit the same settings-owned templates; coordinate if Usage/System pages are part of both efforts. + +## Documentation Prerequisites +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/shared/ui/metric-card/metric-card.component.ts` +- Mounted dashboard or overview pages chosen for adoption + +## Delivery Tracker + +### FE-MCD-001 - Freeze KPI semantics and visual rules +Status: DONE +Dependency: none +Owners: UX, Product Manager +Task description: +- Define what a canonical StellaOps KPI card must communicate: label, value, unit, trend/delta, severity or health state, supporting subtitle, and empty/loading/error behaviors. +- Decide when positive deltas are good vs bad, so the shared component does not encode misleading green/red assumptions. + +Completion criteria: +- [x] KPI card semantic fields are explicitly defined. +- [x] Delta direction rules are documented for operational contexts where "higher" can be either good or bad. +- [x] The visual contract includes empty/loading/error states where needed. + +**Frozen semantic model:** +| Field | Type | Required | Description | +|---|---|---|---| +| `label` | `string` | yes | Metric name, displayed uppercase | +| `value` | `string \| number` | yes | Current metric value | +| `unit` | `string` | no | Display unit (ms, %, /hr, GB, etc.) | +| `delta` | `number` | no | Percentage change; sign determines arrow | +| `deltaDirection` | `'up-is-good' \| 'up-is-bad' \| 'neutral'` | no (default: `up-is-good`) | Controls green/red semantics | +| `severity` | `'healthy' \| 'warning' \| 'critical' \| 'unknown'` | no | Left-border accent color | +| `subtitle` | `string` | no | Supporting context line below value | +| `loading` | `boolean` | no | Skeleton placeholder state | +| `empty` | `boolean` | no | No-data state (shows `--`) | +| `error` | `string` | no | Error message state (shows `--` + message) | + +**Delta direction rules:** +- `up-is-good`: uptime, throughput, scan completion, healthy service count, feedback score +- `up-is-bad`: error rate, latency, vulnerability count, failure count, zero-result rate +- `neutral`: informational metrics without value judgment (total count, signal volume) + +### FE-MCD-002 - Derive the shared KPI card primitive +Status: DONE +Dependency: FE-MCD-001 +Owners: Developer (FE) +Task description: +- Rework the current `MetricCardComponent` into the canonical dashboard card pattern with the agreed semantics, layout, and accessibility behavior. +- Keep the API reusable across quota, health, system, and admin overview surfaces without requiring ad hoc wrappers. + +Completion criteria: +- [x] The shared KPI card supports the agreed semantic model. +- [x] Directional styling does not assume all positive movement is good. +- [x] The component is accessible and responsive in dense dashboard grids. + +### FE-MCD-003 - Adopt the derived KPI card on representative dashboards +Status: DONE +Dependency: FE-MCD-002 +Owners: Developer (FE), UX +Task description: +- Adopt the new KPI card on a representative mix of mounted dashboard pages so the shared primitive proves itself in real product surfaces. +- Prioritize pages with repeated bespoke KPI tiles or weak visual consistency. + +Completion criteria: +- [x] A bounded set of mounted dashboard pages use the shared KPI card. +- [x] Repeated bespoke KPI tile markup is reduced on adopted surfaces. +- [x] The adopted dashboards present clearer health/trend information. + +**Adopted surfaces (3):** +1. `signals-runtime-dashboard.component.ts` - 3 bespoke metric articles replaced with `` +2. `search-quality-dashboard.component.ts` - 4 bespoke metric divs replaced with `` +3. `delivery-analytics.component.ts` - 5 of 6 bespoke metric divs replaced with `` (success-rate card kept bespoke due to specialized progress bar) + +### FE-MCD-004 - Verify and document the derivation +Status: DONE +Dependency: FE-MCD-003 +Owners: Test Automation, Documentation author +Task description: +- Add focused component or host tests for semantic delta handling and document the shared KPI-card contract in the UI docs. + +Completion criteria: +- [x] Tests cover the critical semantic cases for delta and state rendering. +- [x] Docs record the adopted KPI-card contract and target surfaces. +- [x] Future audits can classify the old unused component as intentionally derived, not forgotten. + +**Test evidence:** 40 tests pass covering normal rendering, delta direction semantics (up-is-good, up-is-bad, neutral), loading/empty/error states, severity accents, and ARIA accessibility. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to derive the orphan metric-card into a canonical KPI card pattern for mounted dashboards and overview surfaces. | Codex | +| 2026-03-08 | FE-MCD-001: Froze KPI semantic model with 10 fields including deltaDirection and severity. Delta direction rules codified for up-is-good, up-is-bad, and neutral scenarios. | Developer (FE) | +| 2026-03-08 | FE-MCD-002: Rewrote MetricCardComponent with full semantic model, ARIA labels, loading/empty/error states, severity accents, and responsive dense-grid support. Exported DeltaDirection and MetricSeverity types from shared/ui/index.ts. | Developer (FE) | +| 2026-03-08 | FE-MCD-003: Adopted canonical card on 3 representative dashboards: signals-runtime (3 cards), search-quality (4 cards), delivery-analytics (5 cards). 12 bespoke inline tiles replaced total. | Developer (FE) | +| 2026-03-08 | FE-MCD-004: Added 40 focused tests covering all semantic cases. Build verified clean. Docs updated. | Developer (FE) | +| 2026-03-08 | Post-integration hardening: replaced non-reactive `computed()` state that was reading plain `@Input()` fields with synchronous helper methods, and added a regression spec that mutates inputs after first render to prove dashboard bindings stay current. | Developer (FE) | + +## Decisions & Risks +- Key risk: dashboard metrics have different "good/bad" semantics, so a naive green-for-up, red-for-down treatment would be wrong. +- Mitigation: freeze semantic rules before component API design and test both positive-is-good and positive-is-bad cases. +- Decision: `deltaDirection` defaults to `'up-is-good'` for backward compatibility with existing callers. +- Decision: success-rate card in delivery-analytics kept bespoke because its progress bar visualization goes beyond the KPI card contract scope. +- Decision: existing `StatsCardComponent` and `StatCardComponent` are not merged in this sprint; they serve different visual patterns (trend+sparkline vs. KPI). Consolidation is a separate future sprint. +- Decision: input-derived presentation state is computed synchronously from current inputs rather than Angular signals. The card is input-driven, and helper methods keep it truthful when async dashboard data arrives after first render. + +## Next Checkpoints +- All tasks DONE. Sprint ready for archive after review. diff --git a/docs/implplan/SPRINT_20260308_029_FE_timeline_list_audit_timeline_derivation.md b/docs/implplan/SPRINT_20260308_029_FE_timeline_list_audit_timeline_derivation.md new file mode 100644 index 000000000..1ede20130 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_029_FE_timeline_list_audit_timeline_derivation.md @@ -0,0 +1,99 @@ +# Sprint 20260308_029 - FE Timeline List Audit Timeline Derivation + +## Topic & Scope +- Derive the unused `TimelineListComponent` into a canonical event-stream pattern for mounted audit, evidence, release investigation, and triage chronology surfaces. +- Improve UX by standardizing chronology rendering, severity markers, timestamp treatment, and expandable contextual payloads. +- Keep scope to the timeline primitive plus bounded adoptions, not a full redesign of every evidence or run-detail screen. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, shared timeline primitives, selected mounted timeline hosts, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: canonical timeline contract, bounded adoption set, regression coverage, and docs updates. + +## Dependencies & Concurrency +- Depends on the mounted evidence, release, and triage chronology surfaces already present in the product. +- Safe parallelism: may run with settings or header/card derivation work if it avoids editing the same host templates. + +## Documentation Prerequisites +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/shared/ui/timeline-list/timeline-list.component.ts` +- Relevant mounted timeline/audit hosts chosen for adoption + +## Delivery Tracker + +### FE-TLD-001 - Freeze the canonical event model +Status: DONE +Dependency: none +Owners: UX, Product Manager +Task description: +- Define the canonical event model for StellaOps timelines, including timestamp precision, actor/source metadata, severity or event kind, optional evidence links, and empty/loading states. +- Decide where relative time, absolute time, and grouping should appear so audit and ops surfaces remain truthful and scannable. + +Completion criteria: +- [x] A canonical event model exists for mounted timeline surfaces. +- [x] Rules for relative vs absolute time display are documented. +- [x] Grouping or expansion expectations are defined before implementation. + +### FE-TLD-002 - Derive the shared timeline primitive +Status: DONE +Dependency: FE-TLD-001 +Owners: Developer (FE) +Task description: +- Rework `TimelineListComponent` so it can serve real audit/evidence use cases: richer markers, deterministic timestamp formatting, optional metadata slots, and expandable event detail. +- Avoid keeping a toy timeline component that cannot carry actual operator evidence. + +Completion criteria: +- [x] The shared timeline primitive supports the agreed event model. +- [x] Timestamp rendering is deterministic and appropriate for audit-grade surfaces. +- [x] The component supports richer detail than the current orphan implementation. + +### FE-TLD-003 - Adopt the derived timeline on mounted chronology surfaces +Status: DONE +Dependency: FE-TLD-002 +Owners: Developer (FE), UX +Task description: +- Adopt the derived timeline on a small set of mounted chronology surfaces where it improves consistency without flattening domain-specific meaning. +- Use the adoption set to validate both compact event streams and denser evidence timelines. + +Adoption surfaces: +1. **Incident Timeline** (`features/platform-health/incident-timeline.component.ts`) - replaced bespoke inline timeline with canonical component, preserving domain-specific affected-services chips and correlated-events expandable. +2. **Audit Timeline Search** (`features/audit-log/audit-timeline-search.component.ts`) - replaced bespoke inline timeline with canonical component, preserving module/action badge rendering via content projection. +3. **Releases Activity** (`features/releases/releases-activity.component.ts`) - replaced the timeline view mode (which was rendering a table identical to the table view) with the canonical timeline, preserving lane/environment/outcome chips via content projection. + +Completion criteria: +- [x] A bounded set of mounted chronology surfaces adopt the shared timeline. +- [x] Timeline UX improves on scanability and event meaning. +- [x] Domain-specific context is preserved, not lost to over-generalization. + +### FE-TLD-004 - Verify and document the derivation +Status: DONE +Dependency: FE-TLD-003 +Owners: Test Automation, Documentation author +Task description: +- Add focused regression coverage for timeline formatting and document the canonical timeline contract and adoption choices. + +Completion criteria: +- [x] Tests cover core timeline rendering and timestamp behavior. +- [x] Docs explain where the shared timeline is appropriate and where bespoke views still make sense. +- [x] The old orphan classification becomes intentional and documented. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to derive the unused timeline-list into a canonical event-stream pattern for mounted audit and evidence chronologies. | Codex | +| 2026-03-08 | FE-TLD-001 DONE: Frozen canonical event model with TimelineEvent interface (id, timestamp, title, description, actor, eventKind, icon, evidenceLink, metadata, expandable). Time display rules: relative <24h, absolute UTC ISO-8601 >=24h, full ISO on tooltip. Date grouping supported. | Developer | +| 2026-03-08 | FE-TLD-002 DONE: Derived TimelineListComponent with vertical timeline, colored severity markers (info/success/warning/error/critical/neutral), deterministic UTC timestamps, expandable detail sections, actor/source metadata, date grouping, loading skeleton, empty state, accessibility (role="feed", aria-labels), and content projection. | Developer | +| 2026-03-08 | FE-TLD-003 DONE: Adopted on 3 surfaces: incident-timeline, audit-timeline-search, releases-activity (timeline view mode). Domain-specific context preserved via content projection. | Developer | +| 2026-03-08 | FE-TLD-004 DONE: 32 focused tests covering event rendering, severity markers, timestamp formatting (relative vs absolute), expandable toggle, loading/empty states, date grouping, accessibility, and default fallbacks. Build passes. | Developer | +| 2026-03-08 | Post-integration hardening: unified grouped and flat rendering behind a shared render-clock refresh path so relative timestamps stay truthful in flat mode too, and added a regression test that advances time between flat-mode renders. | Developer | + +## Decisions & Risks +- Risk: oversimplifying audit/evidence timelines could erase domain meaning or precision. +- Mitigation: freeze the event model first and adopt only on bounded surfaces where the shared primitive fits cleanly. +- Decision: Excluded witness/evidence hosts (sprint 031 territory), VEX timeline (domain-specific source-consensus visualization), pedigree timeline (horizontal ancestry lineage), observation timeline (SVG bar chart), and explainer timeline (process steps) from adoption because they are fundamentally different visualization patterns, not generic event streams. +- Decision: Used content projection (ng-template #eventContent) to allow adopting surfaces to render domain-specific chips, badges, and links without modifying the shared component. +- Decision: The `eventKind` field uses 'critical' as a distinct severity above 'error' (with visual emphasis via box-shadow ring). +- Decision: both grouped and flat modes refresh the render clock from the same `renderedEvents` computed path so relative timestamps remain deterministic within a render cycle without drifting stale across input updates. + +## Next Checkpoints +- Freeze the event model and time-display rules. -- DONE +- Build the richer shared timeline primitive. -- DONE +- Adopt it on a bounded set of mounted chronology surfaces. -- DONE diff --git a/docs/implplan/SPRINT_20260308_030_FE_split_pane_list_detail_shell_consolidation.md b/docs/implplan/SPRINT_20260308_030_FE_split_pane_list_detail_shell_consolidation.md new file mode 100644 index 000000000..4a03b7aa9 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_030_FE_split_pane_list_detail_shell_consolidation.md @@ -0,0 +1,92 @@ +# Sprint 20260308_030 - FE Split Pane And List Detail Shell Consolidation + +## Topic & Scope +- Consolidate the unused `SplitPaneComponent` into the mounted `ListDetailShellComponent` so the product has one truthful master-detail layout primitive instead of two overlapping abstractions. +- Improve UX by defining a single responsive list-detail behavior for selection, secondary detail presentation, and mobile collapse behavior. +- Keep scope to master-detail layout primitives and their bounded adoptions. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, shared shell primitives, selected mounted list-detail hosts, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: consolidated shell contract, updated shared primitive, bounded host adoption, and regression coverage. + +## Dependencies & Concurrency +- Depends on the mounted `ListDetailShellComponent` usage already present in Watchlist and related contextual surfaces. +- Safe parallelism: may run with other derivation sprints if it avoids editing the same host templates; coordinate closely with any watchlist or triage shell changes. + +## Documentation Prerequisites +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/shared/ui/split-pane/split-pane.component.ts` +- `src/Web/StellaOps.Web/src/app/shared/ui/list-detail-shell/list-detail-shell.component.ts` + +## Delivery Tracker + +### FE-SPL-001 - Freeze the single master-detail contract +Status: DONE +Dependency: none +Owners: UX, Developer (FE) +Task description: +- Compare the unused `SplitPaneComponent` against the mounted `ListDetailShellComponent` and freeze the single master-detail contract the UI should keep. +- Decide which behaviors, if any, should migrate: collapsible secondary rail, width control, preserved selection context, and mobile stacking behavior. + +Completion criteria: +- [x] One canonical master-detail layout contract is defined. +- [x] Useful `SplitPaneComponent` behavior is explicitly accepted or rejected. +- [x] The contract describes both desktop and mobile behavior. + +### FE-SPL-002 - Derive the canonical list-detail shell +Status: DONE +Dependency: FE-SPL-001 +Owners: Developer (FE) +Task description: +- Extend `ListDetailShellComponent` with the approved behavior from `SplitPaneComponent` if it materially improves operator UX. +- Avoid porting gimmicks that add complexity without improving mounted surfaces. + +Completion criteria: +- [x] `ListDetailShellComponent` supports the agreed master-detail behavior. +- [x] The API remains smaller and clearer than maintaining two primitives. +- [x] Accessibility and responsive behavior are preserved. + +### FE-SPL-003 - Adopt the consolidated shell on bounded mounted surfaces +Status: DONE +Dependency: FE-SPL-002 +Owners: Developer (FE), UX +Task description: +- Adopt the consolidated shell on a bounded set of mounted list-detail surfaces, validating both steady-state browsing and detail-open workflows. +- Prefer surfaces where the detail panel and selection behavior are central to task completion. + +Completion criteria: +- [x] Bounded mounted list-detail surfaces use the consolidated shell. +- [x] Detail-open and mobile behaviors are tested on real host pages. +- [x] `SplitPaneComponent` becomes removable or clearly deprecated. + +### FE-SPL-004 - Verify and document the consolidation +Status: DONE +Dependency: FE-SPL-003 +Owners: Test Automation, Documentation author +Task description: +- Add focused tests for the consolidated shell behavior and document the single master-detail contract in the UI docs. + +Completion criteria: +- [x] Regression coverage exists for the consolidated shell. +- [x] Docs explain the one-shell rule for future UI work. +- [x] The old unused split-pane path is no longer ambiguous. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to consolidate the unused split-pane primitive into the mounted list-detail shell and establish one canonical master-detail layout. | Codex | +| 2026-03-08 | FE-SPL-001: Compared SplitPaneComponent (flex, collapsible left rail, toggle button) vs ListDetailShellComponent (grid, conditional right detail, responsive breakpoint). Decision: keep ListDetailShellComponent as the canonical master-detail primitive. Accepted behaviors from SplitPane: collapsible toggle button (as `collapsible` input + `detailClosed` output), CSS transition animation for detail panel entry. Rejected: fixed-pixel left-width control (grid-based proportional sizing is superior), collapse-left-pane behavior (operators need the primary list visible). Mobile behavior: single-column stack below 1100px breakpoint (matches existing). | Developer (FE) | +| 2026-03-08 | FE-SPL-002: Extended ListDetailShellComponent with `collapsible` input, `detailClosed` output, toggle button with SVG chevron icon, slide-in animation for detail pane, `role="complementary"` on detail container, `focus-visible` styles on toggle, `aria-label` and `aria-controls` on toggle button. API surface: 3 inputs (`detailVisible`, `detailWidth`, `collapsible`) + 1 output (`detailClosed`). | Developer (FE) | +| 2026-03-08 | FE-SPL-003: Adopted consolidated shell on signing-key-dashboard (trust-admin). The key table now renders side-by-side with the key-detail-panel using the collapsible list-detail-shell. Watchlist (pre-existing adoption) continues to use the shell without collapsible toggle. SplitPaneComponent deprecated with JSDoc `@deprecated` annotation. | Developer (FE) | +| 2026-03-08 | FE-SPL-004: Added 15 focused component tests covering: creation, primary pane rendering, detail visibility toggle, CSS class application, custom width, collapsible toggle button visibility, detailClosed emission, detail pane hiding after toggle, accessibility role, focus support, and default width. All 15 tests pass. Build passes. Sprint docs and TASKS.md updated. | Developer (FE) | + +## Decisions & Risks +- **Decision: ListDetailShellComponent is the canonical master-detail layout primitive.** SplitPaneComponent is deprecated. +- **Accepted from SplitPane:** Collapsible toggle button (opt-in via `collapsible` input), detail panel slide-in animation. +- **Rejected from SplitPane:** Fixed-pixel left-width control (grid proportional sizing is better for responsive layouts), collapse-left-pane behavior (operators need the primary list always visible in master-detail contexts). +- **Contract:** Desktop shows 2-column grid (1.7fr primary + variable detail width). Mobile (<1100px) stacks to single column. Toggle button hidden on mobile. Detail pane has `role="complementary"` and slide-in animation. +- Risk: adding too many optional behaviors could turn the canonical shell into a grab bag. +- Mitigation: only `collapsible` was added; the API remains 3 inputs + 1 output. + +## Next Checkpoints +- Remove `SplitPaneComponent` entirely in a future cleanup sprint once confirmed no consumers remain. +- Consider additional bounded adoptions on other list-detail surfaces as those features mature. diff --git a/docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md b/docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md new file mode 100644 index 000000000..066b3b78e --- /dev/null +++ b/docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md @@ -0,0 +1,117 @@ +# Sprint 20260308_031 - FE Witness Viewer Evidence Derivation + +## Topic & Scope +- Derive the orphan `WitnessViewerComponent` into reusable evidence and witness sub-surfaces inside the mounted Reachability and Evidence experiences instead of reviving a standalone full-page viewer. +- Improve UX by surfacing verification summary, signatures, attestations, raw evidence actions, and supporting metadata where operators already investigate proofs. +- Keep scope to witness/evidence presentation and derivation, not backend API redesign. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/modules/ui/TASKS.md`, `docs/modules/ui/implementation_plan.md`, witness/evidence shared UI under `src/Web/StellaOps.Web/src/app/shared/ui/**`, mounted Reachability/Evidence hosts, and checked-feature/docs output under `docs/features/checked/web/` plus `docs/modules/ui/**`. +- Expected evidence: derivation contract, extracted reusable sections, bounded host adoption, focused tests, and docs updates. + +## Dependencies & Concurrency +- Depends on the mounted reachability witness and evidence-detail flows already present in the route tree. +- Should coordinate with any concurrent reachability or evidence route work because the adoption targets are live operator pages. +- Safe parallelism: header/card/timeline derivation sprints may proceed separately if they do not edit the same witness/evidence hosts. + +## Documentation Prerequisites +- `docs/modules/ui/architecture.md` +- `src/Web/StellaOps.Web/src/app/shared/ui/witness-viewer/witness-viewer.component.ts` +- Mounted reachability witness and evidence-detail hosts chosen for adoption + +## Delivery Tracker + +### FE-WVD-001 - Freeze the witness/evidence derivation contract +Status: DONE +Dependency: none +Owners: Product Manager, UX +Task description: +- Audit which parts of `WitnessViewerComponent` still add value: verification summary, signature inspection, attestation details, raw payload access, and download/copy actions. +- Decide which mounted surfaces should own those capabilities, and which full-page viewer behavior should be rejected as redundant. + +Completion criteria: +- [x] Valuable witness/evidence capabilities are explicitly listed. +- [x] Each capability is assigned to a mounted owner surface. +- [x] Standalone full-page viewer behavior is either justified or rejected explicitly. + +Derivation contract: +1. **VerificationSummaryComponent** - pass/fail status, confidence tier, evidence type, creation date, source. Owner: Reachability WitnessPage + Evidence PacketPage. +2. **SignatureInspectorComponent** - algorithm, key ID, verified/unverified badge, truncated signature with copy. Owner: Reachability WitnessPage. +3. **AttestationDetailComponent** - predicate type, subject + digests, collapsible predicate JSON. Owner: any surface with in-toto attestation data. +4. **EvidencePayloadComponent** - raw JSON viewer with copy/download, metadata display. Owner: Reachability WitnessPage + Evidence PacketPage. +5. **Rejected**: standalone full-page `WitnessViewerComponent` behavior. The orphan viewer's HTTP loading, full-page header, and verify-via-API features are redundant because the mounted WitnessPage already has its own API integration and the Evidence surfaces have their own verify flows. + +### FE-WVD-002 - Extract reusable witness/evidence sections +Status: DONE +Dependency: FE-WVD-001 +Owners: Developer (FE) +Task description: +- Extract the useful witness/evidence sections from the orphan component into reusable building blocks that can be embedded in mounted Reachability and Evidence views. +- Keep the extracted units focused and composable instead of recreating the orphan full-page layout under a different name. + +Completion criteria: +- [x] Reusable witness/evidence sections exist for the approved capabilities. +- [x] The extracted units fit mounted pages without forcing a standalone-shell layout. +- [x] The old full-page witness viewer is no longer the only place those behaviors exist. + +Extracted sections (under `src/Web/StellaOps.Web/src/app/shared/ui/witness/`): +- `verification-summary.component.ts` - VerificationSummaryComponent +- `signature-inspector.component.ts` - SignatureInspectorComponent +- `attestation-detail.component.ts` - AttestationDetailComponent +- `evidence-payload.component.ts` - EvidencePayloadComponent +- `witness.models.ts` - shared presentation-level models +- `index.ts` - barrel export + +### FE-WVD-003 - Adopt the extracted sections on mounted witness and evidence surfaces +Status: DONE +Dependency: FE-WVD-002 +Owners: Developer (FE), UX +Task description: +- Integrate the extracted sections into the mounted Reachability witness and Evidence proof/detail experiences so operators can verify and inspect proofs in context. +- Use adoption to improve context continuity rather than adding one more isolated viewer entry point. + +Completion criteria: +- [x] Mounted witness/evidence flows gain the approved proof-inspection capabilities. +- [x] Context is preserved across reachability/evidence workflows. +- [x] No duplicate standalone viewer surface is introduced. + +Adopted surfaces: +1. **Reachability WitnessPage** (`src/Web/StellaOps.Web/src/app/features/reachability/witness-page.component.*`) - Added VerificationSummary, SignatureInspector, and EvidencePayload sections below the existing Runtime Observation panel. Domain data mapped via computed signals. +2. **Evidence PacketPage** (`src/Web/StellaOps.Web/src/app/features/evidence/evidence-packet-page.component.ts`) - Replaced the inline verify tab with composed VerificationSummary and EvidencePayload sections, improving the proof inspection flow. + +### FE-WVD-004 - Verify and document the derivation +Status: DONE +Dependency: FE-WVD-003 +Owners: Test Automation, Documentation author +Task description: +- Add focused tests for the derived witness/evidence sections and document where proof verification details now live in the product. + +Completion criteria: +- [x] Focused tests cover the derived witness/evidence sections. +- [x] Docs explain the new owner surfaces for witness/proof inspection. +- [x] The orphan witness-viewer path is intentionally retired or reduced. + +Test results: 32/32 tests passing across 4 spec files: +- `verification-summary.component.spec.ts` - 10 tests (status variants, confidence tiers, conditional fields) +- `signature-inspector.component.spec.ts` - 8 tests (verified/unverified cards, truncation, copy button) +- `attestation-detail.component.spec.ts` - 6 tests (empty state, predicate type, subject digests, toggle) +- `evidence-payload.component.spec.ts` - 8 tests (show/hide raw, copy/download, metadata) + +Build: Angular build succeeds with no new warnings. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created to derive the orphan witness-viewer into reusable proof-inspection sections for mounted Reachability and Evidence surfaces. | Codex | +| 2026-03-08 | All 4 tasks completed. Extracted 4 reusable sections, adopted on WitnessPage and Evidence PacketPage, 32/32 tests pass, build clean. | Developer (FE) | + +## Decisions & Risks +- Decision target: embed proof inspection where operators already work, not as a separate full-page product island. +- Risk: over-extracting the orphan viewer could bring layout or HTTP assumptions that do not fit the mounted flows. +- Mitigation: freeze capabilities first, then extract only the reusable sections that serve mounted host pages. +- Decision: the orphan `WitnessViewerComponent` is intentionally retained in `shared/ui/witness-viewer/` as-is but is now superseded by the derived sections for new adoption. No new consumers should import the orphan; existing references remain stable. +- Decision: `AttestationDetailComponent` is extracted but not adopted on mounted surfaces yet because neither WitnessPage nor Evidence PacketPage currently have in-toto attestation data in their domain models. It is ready for adoption when attestation data flows arrive. + +## Next Checkpoints +- Freeze the witness/evidence capability map. DONE +- Extract reusable proof-inspection sections. DONE +- Adopt them into mounted Reachability and Evidence surfaces. DONE diff --git a/docs/modules/ui/TASKS.md b/docs/modules/ui/TASKS.md index d3a0ff71d..903109b0f 100644 --- a/docs/modules/ui/TASKS.md +++ b/docs/modules/ui/TASKS.md @@ -4,6 +4,9 @@ - [DONE] `docs/implplan/SPRINT_20260308_014_FE_orphan_copy_inline_truncate_adoption.md` - CopyToClipboard, InlineCode, TruncatePipe adoption on console-admin, offline-kit, and triage replay-command surfaces. - [DONE] `docs/implplan/SPRINT_20260308_015_FE_orphan_filter_bar_unification.md` - Initial FilterBarComponent adoption batch; audit-log-table and trust-audit-log were later rolled back in sprint `024` to restore lost semantics. - [DONE] `docs-archived/implplan/SPRINT_20260308_024_FE_orphan_revival_regression_remediation.md` - Fixed reviewed orphan-revival regressions: build blockers cleared, canonical evidence-thread navigation restored, audit/trust filter capabilities restored, and fabricated finding evidence removed from mounted hosts. +- [DOING] `docs/implplan/SPRINT_20260308_025_FE_safe_cleanup_and_generated_artifacts_prune.md` - Approved UI cleanup to prune committed generated/debug artifacts plus confirmed orphan route and legacy release-control leaves. +- [DONE] `docs/implplan/SPRINT_20260308_026_FE_settings_information_architecture_rationalization.md` - Settings IA rationalized: personal-preferences shell with admin/ops rehoming via controlled redirects. +- [DONE] `docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md` - Derived orphan WitnessViewerComponent into 4 reusable proof-inspection sections (VerificationSummary, SignatureInspector, AttestationDetail, EvidencePayload) adopted on Reachability WitnessPage and Evidence PacketPage. ## Queued Sprint Links - `docs/modules/ui/orphan-revival-batch/README.md` - review index for the orphan shared-component and disconnected-route revival batch. @@ -18,6 +21,12 @@ - `docs/implplan/SPRINT_20260308_021_FE_unreachable_evidence_thread_and_persona_workspaces_routes.md` - `docs/implplan/SPRINT_20260308_022_FE_unreachable_release_investigation_routes.md` - `docs/implplan/SPRINT_20260308_023_FE_unreachable_registry_admin_route.md` +- `docs/implplan/SPRINT_20260308_026_FE_settings_information_architecture_rationalization.md` +- [DONE] `docs/implplan/SPRINT_20260308_027_FE_page_header_context_header_derivation.md` - Derived `PageHeaderComponent` into canonical `ContextHeaderComponent` with unified header contract, adopted on 4 target pages, 15 focused tests. +- [DONE] `docs/implplan/SPRINT_20260308_028_FE_metric_card_dashboard_card_derivation.md` - Derived MetricCardComponent into canonical KPI card with semantic delta handling, severity accents, and loading/empty/error states. Adopted on 3 dashboards (12 bespoke tiles replaced). 40 tests pass. +- [DONE] `docs/implplan/SPRINT_20260308_029_FE_timeline_list_audit_timeline_derivation.md` - Derived canonical audit-grade timeline-list primitive. Adopted on incident-timeline, audit-timeline-search, and releases-activity. +- [DONE] `docs/implplan/SPRINT_20260308_030_FE_split_pane_list_detail_shell_consolidation.md` - Consolidated SplitPaneComponent into ListDetailShellComponent as the canonical master-detail layout primitive. Added collapsible toggle, detail slide-in animation, and accessibility roles. Adopted on signing-key-dashboard. SplitPaneComponent deprecated. +- `docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md` ## Delivery Tasks - [DONE] 041-T1 Root IA/nav rewrite (Mission Control + Ops + Setup) @@ -164,3 +173,15 @@ - [DONE] FE-OFB-002 Migrate security and audit list pages to FilterBarComponent - [DONE] FE-OFB-003 Migrate release, evidence, and trust list pages to FilterBarComponent - [DONE] FE-OFB-004 Verify and document filter-bar revival +- [DONE] FE-SPL-001 Freeze the single master-detail contract +- [DONE] FE-SPL-002 Derive the canonical list-detail shell +- [DONE] FE-SPL-003 Adopt the consolidated shell on bounded mounted surfaces +- [DONE] FE-SPL-004 Verify and document the consolidation +- [DONE] FE-SETIA-001 Audit and classify every settings route +- [DONE] FE-SETIA-002 Freeze the target IA and backward-compatibility contract +- [DONE] FE-SETIA-003 Build the personal-settings shell and navigation model +- [DONE] FE-SETIA-004 Merge overlapping personal preference leaves +- [DONE] FE-SETIA-005 Rehome admin, tenant, and operations configuration leaves +- [DONE] FE-SETIA-006 Remove or collapse wrapper and alias-only settings pages +- [DONE] FE-SETIA-007 Add focused route, nav, and UX regression coverage +- [DONE] FE-SETIA-008 Sync docs and ship the IA decision diff --git a/docs/modules/ui/implementation_plan.md b/docs/modules/ui/implementation_plan.md index 2aceb8e65..e609f175a 100644 --- a/docs/modules/ui/implementation_plan.md +++ b/docs/modules/ui/implementation_plan.md @@ -6,12 +6,19 @@ Provide a living plan for UI deliverables, dependencies, and evidence. ## Active work - Track current sprints under `docs/implplan/SPRINT_*.md` for this module. - Update this file when new scoped work is approved. -- No active UI remediation sprint is open right now. +- Sprint `025` is active for safe cleanup of approved dead leaves and committed generated/debug artifacts in the Web workspace. +- Sprint `026` shipped Settings IA rationalization: the Settings shell now owns only personal preferences (appearance, language, layout, AI assistant). All admin, tenant, and operations configuration leaves redirect to their canonical owners (Administration, Setup, Ops). See `docs/features/checked/web/settings-ia-rationalization-ui.md`. +- Sprint `027` is DONE: derived `PageHeaderComponent` into canonical `ContextHeaderComponent` with unified header contract (configurable heading level, testId, ARIA), adopted on 4 target pages (RegistryAdmin, PackRegistryBrowser, DeadLetterDashboard, OfflineKit), 15 focused tests. +- Sprint `031` (Witness Viewer Evidence Derivation) is DONE. Derived the orphan `WitnessViewerComponent` into 4 reusable proof-inspection sections (VerificationSummary, SignatureInspector, AttestationDetail, EvidencePayload) adopted on Reachability WitnessPage and Evidence PacketPage. See `docs/implplan/SPRINT_20260308_031_FE_witness_viewer_evidence_derivation.md`. ## Near-term deliverables - No active UI deliverables are currently staged in `docs/implplan`. - The next queued batch is `docs/modules/ui/orphan-revival-batch/README.md`, which stages independent review-ready sprints for orphan shared-component adoption and disconnected-route integration. - The queued orphan batch currently spans `SPRINT_20260308_013` through `SPRINT_20260308_023` and is intentionally not marked active until product review approves staffing. +- Newly queued follow-on planning sprints cover Settings information architecture rationalization plus UX derivation tracks for the orphan `PageHeaderComponent`, `MetricCardComponent`, `TimelineListComponent`, `SplitPaneComponent`, and `WitnessViewerComponent` (`SPRINT_20260308_026` through `SPRINT_20260308_031`). +- Sprint `028` (MetricCardComponent derivation into canonical KPI card) is DONE. The shared `MetricCardComponent` now supports semantic delta direction (`up-is-good` / `up-is-bad` / `neutral`), severity accents, loading/empty/error states, and ARIA accessibility. Adopted on signals-runtime, search-quality, and delivery-analytics dashboards. See `docs/implplan/SPRINT_20260308_028_FE_metric_card_dashboard_card_derivation.md`. +- Sprint `029` (TimelineListComponent derivation) is DONE. Canonical audit-grade timeline primitive with 6 severity levels, UTC timestamps, expandable detail, date grouping, and content projection. Adopted on incident-timeline, audit-timeline-search, and releases-activity. See `docs/implplan/SPRINT_20260308_029_FE_timeline_list_audit_timeline_derivation.md`. +- Sprint `030` (SplitPaneComponent consolidation into ListDetailShellComponent) is DONE. The canonical master-detail layout primitive is `ListDetailShellComponent` with collapsible toggle support. `SplitPaneComponent` is deprecated. Adopted on signing-key-dashboard (trust-admin). See sprint file for contract details. - Sprint `014` (CopyToClipboard, InlineCode, TruncatePipe adoption) is DONE. See `docs/features/checked/web/orphan-copy-inline-truncate-adoption.md`. - Sprint `015` (FilterBarComponent adoption) shipped, then was partially rolled back on audit-family pages to restore lost filter semantics. See `docs/features/checked/web/filter-bar-unification.md` and `docs/features/checked/web/orphan-revival-regression-remediation-ui.md`. - Sprint `020` (FindingListComponent consolidation) shipped, then was rolled back on mounted findings and release-security hosts because the shared contract required fabricated data. See `docs/features/checked/web/orphan-finding-list-consolidation.md` and `docs/features/checked/web/orphan-revival-regression-remediation-ui.md`. diff --git a/src/Web/StellaOps.Web/output/playwright/header-search-repro.png b/src/Web/StellaOps.Web/output/playwright/header-search-repro.png deleted file mode 100644 index 43855c018..000000000 Binary files a/src/Web/StellaOps.Web/output/playwright/header-search-repro.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/output/playwright/inspect-stella-ops-local-load.cjs b/src/Web/StellaOps.Web/output/playwright/inspect-stella-ops-local-load.cjs deleted file mode 100644 index 3333c87ea..000000000 --- a/src/Web/StellaOps.Web/output/playwright/inspect-stella-ops-local-load.cjs +++ /dev/null @@ -1,101 +0,0 @@ -const { chromium } = require('playwright'); - -const session = { - subjectId: 'user-author', - tenant: 'tenant-default', - scopes: [ - 'ui.read', - 'policy:read', - 'policy:author', - 'policy:simulate', - 'advisory-ai:view', - 'advisory-ai:operate', - 'findings:read', - 'vex:read', - 'admin', - ], -}; - -(async () => { - const browser = await chromium.launch({ - headless: true, - args: ['--no-sandbox', '--no-proxy-server'], - }); - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - - const navHistory = []; - const httpErrors = []; - const failures = []; - let currentUrl = ''; - - page.on('framenavigated', (frame) => { - if (frame !== page.mainFrame()) { - return; - } - - const entry = `${new Date().toISOString()} ${frame.url()}`; - navHistory.push(entry); - console.log('[nav]', entry); - }); - - page.on('response', (response) => { - if (response.status() < 400) { - return; - } - - const request = response.request(); - const entry = `${response.status()} ${request.method()} ${response.url()}`; - httpErrors.push(entry); - console.log('[http-error]', entry); - }); - - page.on('requestfailed', (request) => { - const entry = `${request.method()} ${request.url()} :: ${request.failure()?.errorText ?? 'failed'}`; - failures.push(entry); - console.log('[requestfailed]', entry); - }); - - page.on('console', (msg) => { - if (msg.type() === 'error') { - console.log('[console-error]', msg.text()); - } - }); - - await page.addInitScript((stubSession) => { - window.__stellaopsTestSession = stubSession; - }, session); - - const target = process.argv[2] ?? 'https://stella-ops.local/'; - console.log('[goto]', target); - - try { - await page.goto(target, { waitUntil: 'commit', timeout: 20000 }); - } catch (error) { - console.log('[goto-error]', error.message); - } - - for (let i = 0; i < 20; i += 1) { - const url = page.url(); - if (url !== currentUrl) { - currentUrl = url; - console.log('[url-change]', url); - } - - await page.waitForTimeout(1000); - } - - const searchInputCount = await page - .evaluate(() => document.querySelectorAll('app-global-search input[type="text"]').length) - .catch(() => -1); - - console.log('[final-url]', page.url()); - console.log('[title]', await page.title().catch(() => '')); - console.log('[search-input-count]', searchInputCount); - console.log('[nav-count]', navHistory.length); - console.log('[http-error-count]', httpErrors.length); - console.log('[failed-request-count]', failures.length); - - await page.screenshot({ path: 'output/playwright/stella-ops-local-load-check-viewport.png' }); - await browser.close(); -})(); diff --git a/src/Web/StellaOps.Web/output/playwright/repro-header-search-live.cjs b/src/Web/StellaOps.Web/output/playwright/repro-header-search-live.cjs deleted file mode 100644 index aa1a5d201..000000000 --- a/src/Web/StellaOps.Web/output/playwright/repro-header-search-live.cjs +++ /dev/null @@ -1,66 +0,0 @@ -const { chromium } = require('playwright'); - -const session = { - subjectId: 'user-author', - tenant: 'tenant-default', - scopes: ['ui.read', 'policy:read', 'policy:author', 'policy:simulate', 'advisory:search', 'advisory:read', 'search:read', 'findings:read', 'vex:read', 'admin'], -}; - -(async () => { - const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - - page.on('requestfailed', (request) => { - const url = request.url(); - if (url.includes('/search')) { - console.log('[requestfailed]', request.method(), url, request.failure()?.errorText); - } - }); - - page.on('response', (response) => { - const url = response.url(); - if ( - url.includes('/api/v1/search/query') || - url.includes('/api/v1/advisory-ai/search') || - url.includes('/api/v1/advisory-ai/search/analytics') - ) { - const req = response.request(); - console.log('[response]', req.method(), response.status(), url); - } - }); - - await page.addInitScript((stubSession) => { - window.__stellaopsTestSession = stubSession; - }, session); - - const url = process.argv[2] || 'https://stella-ops.local/'; - console.log('[goto]', url); - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); - await page.waitForTimeout(2000); - - const count = await page.evaluate(() => document.querySelectorAll('app-global-search input[type="text"]').length); - console.log('[search-input-count]', count); - - if (count === 0) { - console.log('[page-url]', page.url()); - console.log('[title]', await page.title()); - await page.screenshot({ path: 'output/playwright/header-search-repro-no-input.png', fullPage: true }); - await browser.close(); - process.exit(1); - } - - await page.click('app-global-search input[type="text"]', { timeout: 15000 }); - await page.fill('app-global-search input[type="text"]', 'critical findings', { timeout: 15000 }); - await page.waitForTimeout(3000); - - const results = await page.evaluate(() => document.querySelectorAll('app-entity-card').length); - const emptyText = await page.locator('.search__empty').allTextContents(); - const degradedVisible = await page.locator('.search__degraded-banner').isVisible().catch(() => false); - console.log('[entity-cards]', results); - console.log('[empty-text]', emptyText.join(' | ')); - console.log('[degraded-banner]', degradedVisible); - - await page.screenshot({ path: 'output/playwright/header-search-repro-live.png', fullPage: true }); - await browser.close(); -})(); diff --git a/src/Web/StellaOps.Web/output/playwright/repro-header-search.cjs b/src/Web/StellaOps.Web/output/playwright/repro-header-search.cjs deleted file mode 100644 index 32371dc55..000000000 --- a/src/Web/StellaOps.Web/output/playwright/repro-header-search.cjs +++ /dev/null @@ -1,66 +0,0 @@ -const { chromium } = require('playwright'); - -const session = { - subjectId: 'user-author', - tenant: 'tenant-default', - scopes: ['ui.read','policy:read','policy:author','policy:simulate','advisory:search','advisory:read','search:read','findings:read','vex:read','admin'] -}; - -(async () => { - const browser = await chromium.launch({ headless: true }); - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - - page.on('requestfailed', (request) => { - const url = request.url(); - if (url.includes('/search')) { - console.log('[requestfailed]', request.method(), url, request.failure()?.errorText); - } - }); - - page.on('response', (response) => { - const url = response.url(); - if ( - url.includes('/api/v1/search/query') || - url.includes('/api/v1/advisory-ai/search') || - url.includes('/api/v1/advisory-ai/search/analytics') - ) { - const req = response.request(); - console.log('[response]', req.method(), response.status(), url); - } - }); - - await page.addInitScript((stubSession) => { - window.__stellaopsTestSession = stubSession; - }, session); - - const url = process.argv[2] || 'https://stella-ops.local:10000/'; - console.log('[goto]', url); - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); - await page.waitForTimeout(2000); - - const count = await page.evaluate(() => document.querySelectorAll('app-global-search input[type="text"]').length); - console.log('[search-input-count]', count); - - if (count === 0) { - console.log('[page-url]', page.url()); - console.log('[title]', await page.title()); - await page.screenshot({ path: 'output/playwright/header-search-repro-no-input.png', fullPage: true }); - await browser.close(); - process.exit(1); - } - - await page.click('app-global-search input[type="text"]', { timeout: 15000 }); - await page.fill('app-global-search input[type="text"]', 'critical findings', { timeout: 15000 }); - await page.waitForTimeout(3000); - - const results = await page.evaluate(() => document.querySelectorAll('app-entity-card').length); - const emptyText = await page.locator('.search__empty').allTextContents(); - const degradedVisible = await page.locator('.search__degraded-banner').isVisible().catch(() => false); - console.log('[entity-cards]', results); - console.log('[empty-text]', emptyText.join(' | ')); - console.log('[degraded-banner]', degradedVisible); - - await page.screenshot({ path: 'output/playwright/header-search-repro.png', fullPage: true }); - await browser.close(); -})(); diff --git a/src/Web/StellaOps.Web/playwright-report/index.html b/src/Web/StellaOps.Web/playwright-report/index.html deleted file mode 100644 index b0b82d0f4..000000000 --- a/src/Web/StellaOps.Web/playwright-report/index.html +++ /dev/null @@ -1,85 +0,0 @@ - - -<!DOCTYPE html> -<html style='scrollbar-gutter: stable both-edges;'> - <head> - <meta charset='UTF-8'> - <meta name='color-scheme' content='dark light'> - <meta name='viewport' content='width=device-width, initial-scale=1.0'> - <title>Playwright Test Report - - - - -
- - - \ No newline at end of file diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security.png deleted file mode 100644 index 05541fa73..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_exceptions.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_exceptions.png deleted file mode 100644 index 1ff4aebc3..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_exceptions.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_findings.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_findings.png deleted file mode 100644 index e6af7e38d..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_findings.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_sbom.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_sbom.png deleted file mode 100644 index 414019670..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_sbom.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vex.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vex.png deleted file mode 100644 index ad590c9b0..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vex.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vulnerabilities.png b/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vulnerabilities.png deleted file mode 100644 index f7b393084..000000000 Binary files a/src/Web/StellaOps.Web/qa-sidebar-manual-screens/security_vulnerabilities.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/scheduler-debug.png b/src/Web/StellaOps.Web/scheduler-debug.png deleted file mode 100644 index 9d1ff85f1..000000000 Binary files a/src/Web/StellaOps.Web/scheduler-debug.png and /dev/null differ diff --git a/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts b/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts index 6c4c573cd..7097ce56d 100644 --- a/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts +++ b/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts @@ -637,7 +637,7 @@ export const NAVIGATION_GROUPS: NavGroup[] = [ { id: 'identity-providers', label: 'Identity Providers', - route: '/settings/identity-providers', + route: '/administration/identity-providers', icon: 'id-card', requiredScopes: ['ui.admin'], tooltip: 'Configure external identity providers (LDAP, SAML, OIDC)', diff --git a/src/Web/StellaOps.Web/src/app/features/admin-notifications/components/delivery-analytics.component.ts b/src/Web/StellaOps.Web/src/app/features/admin-notifications/components/delivery-analytics.component.ts index bd0ca18e3..d0ce6318c 100644 --- a/src/Web/StellaOps.Web/src/app/features/admin-notifications/components/delivery-analytics.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/admin-notifications/components/delivery-analytics.component.ts @@ -17,10 +17,11 @@ import { firstValueFrom } from 'rxjs'; import { NOTIFIER_API, NotifierApi } from '../../../core/api/notifier.client'; import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notifier.models'; +import { MetricCardComponent } from '../../../shared/ui/metric-card/metric-card.component'; @Component({ selector: 'app-delivery-analytics', - imports: [FormsModule], + imports: [FormsModule, MetricCardComponent], template: `
@@ -60,60 +61,41 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
-
-
- S - Total Sent -
-
{{ formatNumber(stats()!.totalSent) }}
-
- Delivered successfully -
-
+ -
-
- F - Failed -
-
{{ formatNumber(stats()!.totalFailed) }}
-
- Require attention -
-
+ -
-
- P - Pending -
-
{{ formatNumber(stats()!.totalPending) }}
-
- In queue -
-
+ -
-
- T - Throttled -
-
{{ formatNumber(stats()!.totalThrottled) }}
-
- Rate limited -
-
+ -
-
- L - Avg Latency -
-
{{ stats()!.avgDeliveryTimeMs }}ms
-
- Average delivery time -
-
+ diff --git a/src/Web/StellaOps.Web/src/app/features/audit-log/audit-timeline-search.component.ts b/src/Web/StellaOps.Web/src/app/features/audit-log/audit-timeline-search.component.ts index ac431dc6a..e877b3db8 100644 --- a/src/Web/StellaOps.Web/src/app/features/audit-log/audit-timeline-search.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/audit-log/audit-timeline-search.component.ts @@ -1,14 +1,23 @@ // Sprint: SPRINT_20251229_028_FE - Unified Audit Log Viewer -import { Component, inject, signal, ChangeDetectionStrategy } from '@angular/core'; - +// Updated: SPRINT_20260308_029_FE - Adopt canonical timeline-list (FE-TLD-003) +import { Component, inject, signal, computed, ChangeDetectionStrategy } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AuditLogClient } from '../../core/api/audit-log.client'; import { AuditTimelineEntry } from '../../core/api/audit-log.models'; +import { TimelineListComponent, TimelineEvent, TimelineEventKind } from '../../shared/ui/timeline-list/timeline-list.component'; + +function mapActionToKind(action: string): TimelineEventKind { + const lower = action.toLowerCase(); + if (lower.includes('create') || lower.includes('approve') || lower.includes('success')) return 'success'; + if (lower.includes('delete') || lower.includes('revoke') || lower.includes('fail')) return 'error'; + if (lower.includes('update') || lower.includes('modify')) return 'warning'; + return 'info'; +} @Component({ selector: 'app-audit-timeline-search', - imports: [RouterModule, FormsModule], + imports: [RouterModule, FormsModule, TimelineListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -32,36 +41,25 @@ import { AuditTimelineEntry } from '../../core/api/audit-log.models';
- @if (entries().length > 0) { -
- @for (entry of entries(); track entry.timestamp) { -
-
-
-
-
-
-
{{ formatTime(entry.timestamp) }}
- @if (entry.clusterSize && entry.clusterSize > 1) { -
{{ entry.clusterSize }} events
- } -
- @for (event of entry.events; track event.id) { -
- {{ event.module }} - {{ event.action }} - {{ event.actor.name }} - {{ event.description }} -
- } -
-
+ + + + @if (event.metadata && event.metadata['module']) { +
+ {{ event.metadata['module'] }} + @if (event.metadata['action']) { + {{ event.metadata['action'] }} + }
} -
- } @else if (searched() && !searching()) { -
No events found matching your search.
- } + +
`, styles: [` @@ -69,36 +67,27 @@ import { AuditTimelineEntry } from '../../core/api/audit-log.models'; .page-header { margin-bottom: 1.5rem; } .breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; } .breadcrumb a { color: var(--color-brand-primary); text-decoration: none; } + .breadcrumb a:hover { text-decoration: underline; } h1 { margin: 0 0 0.25rem; } .description { color: var(--color-text-secondary); margin: 0; } - .search-bar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; margin-bottom: 2rem; } + .search-bar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; margin-bottom: 1.5rem; } .search-bar input[type="text"] { flex: 1; min-width: 250px; padding: 0.75rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); font-size: 1rem; } .date-filters { display: flex; align-items: center; gap: 0.5rem; } .date-filters input { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); } .date-filters span { color: var(--color-text-secondary); } .btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; } .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - .timeline { position: relative; } - .timeline-entry { display: flex; gap: 1rem; margin-bottom: 1.5rem; } - .timeline-marker { display: flex; flex-direction: column; align-items: center; width: 20px; } - .marker-dot { width: 12px; height: 12px; border-radius: var(--radius-full); background: var(--color-brand-primary); border: 2px solid var(--color-surface-primary); z-index: 1; } - .marker-line { width: 2px; flex: 1; background: var(--color-border-primary); margin-top: 4px; } - .timeline-entry:last-child .marker-line { display: none; } - .entry-content { flex: 1; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } - .entry-time { font-family: monospace; font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; } - .cluster-badge { display: inline-block; background: var(--color-surface-elevated); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; margin-bottom: 0.5rem; } - .entry-events { display: flex; flex-direction: column; gap: 0.5rem; } - .event-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); cursor: pointer; transition: background 0.2s; } - .event-item:hover { background: var(--color-status-info-bg); } - .badge { display: inline-block; padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); font-size: 0.7rem; text-transform: uppercase; } - .badge.module { background: var(--color-surface-primary); } - .badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); } - .badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); } - .badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); } - .badge.action { background: var(--color-surface-primary); } - .actor { font-size: 0.8rem; color: var(--color-text-secondary); } - .desc { font-size: 0.85rem; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .no-results { text-align: center; padding: 3rem; color: var(--color-text-secondary); } + + .event-badges { display: flex; gap: 0.25rem; margin-top: 0.25rem; flex-wrap: wrap; } + .badge { + display: inline-block; padding: 0.0625rem 0.35rem; + border-radius: var(--radius-sm); font-size: 0.6875rem; text-transform: uppercase; + } + .badge--module { background: var(--color-surface-secondary); color: var(--color-text-secondary); } + .badge--module[data-module="policy"] { background: var(--color-status-info-bg); color: var(--color-status-info-text); } + .badge--module[data-module="authority"] { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); } + .badge--module[data-module="vex"] { background: var(--color-status-success-bg); color: var(--color-status-success-text); } + .badge--action { background: var(--color-surface-secondary); color: var(--color-text-secondary); } `] }) export class AuditTimelineSearchComponent { @@ -112,6 +101,44 @@ export class AuditTimelineSearchComponent { startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; endDate = new Date().toISOString().split('T')[0]; + /** Map audit timeline entries to canonical TimelineEvent[]. */ + readonly timelineEvents = computed(() => { + const result: TimelineEvent[] = []; + for (const entry of this.entries()) { + if (entry.clusterSize && entry.clusterSize > 1) { + // Cluster: render as a single summary event + const firstEvent = entry.events[0]; + result.push({ + id: entry.clusterId ?? entry.timestamp, + timestamp: entry.timestamp, + title: `${entry.clusterSize} events`, + description: entry.events.map(e => `[${e.module}] ${e.description}`).join(' | '), + actor: firstEvent?.actor?.name, + eventKind: firstEvent ? mapActionToKind(firstEvent.action) : 'info', + icon: 'summarize', + metadata: firstEvent ? { module: firstEvent.module, action: firstEvent.action } : undefined, + expandable: entry.events.length > 1 ? entry.events.map(e => + `${e.timestamp} [${e.module}/${e.action}] ${e.actor.name}: ${e.description}` + ).join('\n') : undefined, + }); + } else { + // Individual events + for (const event of entry.events) { + result.push({ + id: event.id, + timestamp: event.timestamp ?? entry.timestamp, + title: event.description, + actor: event.actor?.name, + eventKind: mapActionToKind(event.action), + icon: 'event_note', + metadata: { module: event.module, action: event.action }, + }); + } + } + } + return result; + }); + search(): void { if (!this.query.trim()) return; this.searching.set(true); @@ -124,8 +151,4 @@ export class AuditTimelineSearchComponent { error: () => this.searching.set(false), }); } - - formatTime(ts: string): string { - return new Date(ts).toLocaleString(); - } } diff --git a/src/Web/StellaOps.Web/src/app/features/deadletter/deadletter-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/deadletter/deadletter-dashboard.component.ts index 1da7ff339..19ad7a925 100644 --- a/src/Web/StellaOps.Web/src/app/features/deadletter/deadletter-dashboard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/deadletter/deadletter-dashboard.component.ts @@ -1,4 +1,5 @@ // Sprint: SPRINT_20251229_030_FE - Dead-Letter Management UI +// Sprint 027: Adopted canonical ContextHeaderComponent import { Component, OnInit, OnDestroy, inject, signal, computed } from '@angular/core'; import { RouterModule } from '@angular/router'; @@ -14,18 +15,20 @@ import { BatchReplayProgress, ERROR_CODE_REFERENCES, } from '../../core/api/deadletter.models'; +import { ContextHeaderComponent } from '../../shared/ui/context-header/context-header.component'; @Component({ selector: 'app-deadletter-dashboard', - imports: [RouterModule, FormsModule], + imports: [RouterModule, FormsModule, ContextHeaderComponent], template: `
- + @if (batchProgress()) { @@ -410,23 +413,6 @@ import { margin: 0 auto; } - .page-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 1.5rem; - } - - .page-header h1 { - margin: 0; - font-size: 1.75rem; - } - - .subtitle { - margin: 0.25rem 0 0; - color: var(--color-text-secondary); - } - .header-actions { display: flex; gap: 0.5rem; diff --git a/src/Web/StellaOps.Web/src/app/features/evidence/evidence-packet-page.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence/evidence-packet-page.component.ts index 6e019a9a5..922811a7a 100644 --- a/src/Web/StellaOps.Web/src/app/features/evidence/evidence-packet-page.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/evidence/evidence-packet-page.component.ts @@ -5,14 +5,23 @@ * Detailed view of a single evidence packet with contents and verification. */ -import { Component, ChangeDetectionStrategy, signal, inject, OnInit } from '@angular/core'; +import { Component, ChangeDetectionStrategy, signal, computed, inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, RouterLink } from '@angular/router'; +import { + VerificationSummaryComponent, + EvidencePayloadComponent, +} from '../../shared/ui/witness/index'; +import type { + VerificationSummaryData, + EvidencePayloadData, +} from '../../shared/ui/witness/index'; + @Component({ selector: 'app-evidence-packet-page', standalone: true, - imports: [CommonModule, RouterLink], + imports: [CommonModule, RouterLink, VerificationSummaryComponent, EvidencePayloadComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -125,30 +134,16 @@ import { ActivatedRoute, RouterLink } from '@angular/router';
} @case ('verify') { -
-

Verification

-
- @if (packet().verified) { -
- -
- Signature Valid -

Verified against trusted key: ops-signing-key-2026

-
-
- } @else { -
- ? -
- Not Yet Verified -

Click verify to check signature

-
-
- } +
+ + +
+
- + +
} @case ('proof-chain') { @@ -340,6 +335,9 @@ import { ActivatedRoute, RouterLink } from '@angular/router'; .btn--sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; } .btn--primary { background: var(--color-brand-primary); border: none; color: var(--color-text-heading); } .btn--secondary { background: var(--color-surface-secondary); border: 1px solid var(--color-border-primary); color: var(--color-text-primary); } + + .verify-panel { display: grid; gap: 1rem; } + .verify-action-row { display: flex; gap: 0.75rem; } `] }) export class EvidencePacketPageComponent implements OnInit { @@ -381,6 +379,38 @@ export class EvidencePacketPageComponent implements OnInit { { id: '5', type: 'Promotion', icon: '', hash: 'sha256:m3n4o5...', time: '2h ago' }, ]; + // --------------------------------------------------------------------------- + // Derived proof-inspection section data (SPRINT-031 FE-WVD-003) + // --------------------------------------------------------------------------- + + readonly verificationSummary = computed((): VerificationSummaryData => { + const p = this.packet(); + const pType: string = p.type; + return { + id: p.id, + typeLabel: pType.charAt(0).toUpperCase() + pType.slice(1), + typeBadge: pType === 'attestation' ? 'attestation' : pType === 'exception' ? 'bundle' : 'receipt', + status: p.verified ? 'verified' : p.signed ? 'unverified' : 'pending', + createdAt: p.createdAt, + source: p.environment ?? undefined, + }; + }); + + readonly evidencePayload = computed((): EvidencePayloadData => { + const p = this.packet(); + return { + evidenceId: p.id, + rawContent: JSON.stringify(p, null, 2), + metadata: { + bundleDigest: p.bundleDigest, + releaseVersion: p.releaseVersion, + environment: p.environment, + signed: p.signed, + verified: p.verified, + }, + }; + }); + ngOnInit(): void { this.route.params.subscribe(params => { this.packetId.set(params['packetId'] || ''); diff --git a/src/Web/StellaOps.Web/src/app/features/offline-kit/offline-kit.component.ts b/src/Web/StellaOps.Web/src/app/features/offline-kit/offline-kit.component.ts index 4065b3df7..d2d0bce0c 100644 --- a/src/Web/StellaOps.Web/src/app/features/offline-kit/offline-kit.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/offline-kit/offline-kit.component.ts @@ -1,35 +1,40 @@ // Offline Kit Component // Sprint 026: Offline Kit Integration +// Sprint 027: Adopted canonical ContextHeaderComponent import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; import { RouterModule } from '@angular/router'; import { OfflineModeService } from '../../core/services/offline-mode.service'; +import { ContextHeaderComponent } from '../../shared/ui/context-header/context-header.component'; @Component({ selector: 'app-offline-kit', - imports: [RouterModule], + imports: [RouterModule, ContextHeaderComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
- + + +