Broad UI improvements spanning auth, branding, notifications, agents, analytics, approvals, audit-log, bundles, configuration, console-admin, dashboard, deployments, doctor, environments, evidence, feed-mirror, graph, integration-hub, issuer-trust, lineage, notify, offline-kit, policy, promotions, quota, registry, release-orchestrator, releases, sbom, scans, secret-detection, security, settings, setup-wizard, system-health, topology, triage, trust-admin, unknowns, vex-hub, vulnerabilities, and watchlist features. Adds new shared components (page-action-outlet, stella-action-card, stella-form-field), scripts feature module, audit-trust component, e2e test helpers, and release page e2e specs. Updates auth session model, branding service, color tokens, form styles, and i18n translations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15 KiB
StellaOps Web Frontend
Mission
Design and build the StellaOps web user experience that surfaces backend capabilities (Authority, Concelier, Exporters) through an offline-friendly Angular application.
Team Composition
- UX Specialist ??? defines user journeys, interaction patterns, accessibility guidelines, and visual design language.
- Angular Engineers ??? implement the SPA, integrate with backend APIs, and ensure deterministic builds suitable for air-gapped deployments.
Technology Stack
- Framework: Angular 21 (standalone components, signals, built-in control flow)
- Language: TypeScript 5.9
- UI Library: Angular Material 21 + Angular CDK 21
- State: Angular Signals
- Build:
@angular/build:application(esbuild-based) - Unit Tests: Vitest via
@angular/build:unit-testbuilder (Jasmine compatibility shim insrc/test-setup.ts) - E2E Tests: Playwright
- Storybook: Storybook 10 with
@storybook/angular - Node.js: ^20.19.0 || ^22.12.0 || ^24.0.0
Operating Principles
- Favor modular Angular architecture (feature modules, shared UI kit) with strong typing via latest TypeScript/Angular releases.
- Align UI flows with backend contracts; coordinate with Authority and Concelier teams for API changes.
- Keep assets and build outputs deterministic and cacheable for Offline Kit packaging.
- Coordinate cross-module changes via docs/implplan/SPRINT*.md files updates and PR descriptions.
- Console admin flows use Authority
/console/admin/*APIs and enforce fresh-auth for privileged actions. - Branding uses Authority
/console/brandingand applies only whitelisted CSS variables.
Key Paths
src/Web/StellaOps.Web??? Angular workspace (to be scaffolded).docs/??? UX specs and mockups (to be added).ops/??? Web deployment manifests for air-gapped environments (future).
Reachability Drift UI (Sprint 3600)
Components
- PathViewerComponent (
app/features/reachability/components/path-viewer/) - Interactive call path visualization- Displays entrypoint ??? key nodes ??? sink paths
- Highlights changed nodes with change kind indicators
- Supports collapse/expand for long paths
- RiskDriftCardComponent (
app/features/reachability/components/risk-drift-card/) - Summary card for drift analysis- Shows newly reachable / mitigated path counts
- Displays associated CVEs
- Action buttons for drill-down
Models
PathNode- Node in a reachability path with symbol, file, lineCompressedPath- Compact path representationDriftedSink- Sink with reachability change and causeDriftCause- Explanation of why reachability changed
Services
DriftApiService(app/core/services/drift-api.service.ts) - API client for drift endpoints- Mock implementations available for offline development
Integration Points
- Scan detail page includes PathViewer for reachability visualization
- Drift results linked to DSSE attestations for evidence chain
- Path export supports JSON and SARIF formats
Witness UI (Sprint 3700) - TODO
Planned Components
- WitnessModalComponent - Modal for viewing witness details
- PathVisualizationComponent - Detailed path rendering with gates
- ConfidenceTierBadgeComponent - Tier indicators (Confirmed/Likely/Present/Unreachable)
- GateBadgeComponent - Auth gate visualization
Planned Services
witness.service.ts- API client for witness endpoints- Browser-based Ed25519 signature verification
Coordination
- Sync with DevEx for project scaffolding and build pipelines.
- Partner with Docs Guild to translate UX decisions into operator guides.
- Collaborate with Security Guild to validate authentication flows and session handling.
Required Reading
docs/modules/platform/architecture-overview.mddocs/technical/architecture/console-admin-rbac.mddocs/technical/architecture/console-branding.md
Working Agreement
-
- Update task status to
DOING/DONEin both correspoding sprint file/docs/implplan/SPRINT_*.mdwhen you start or finish work.
- Update task status to
-
- Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
-
- Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
-
- Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
-
- Revert to
TODOif you pause the task without shipping changes; leave notes in commit/PR descriptions for context.
- Revert to
Metric / KPI Cards Convention (MANDATORY)
All metric badges, stat cards, KPI tiles, and summary indicators must use <stella-metric-card>.
Do NOT create custom .stat-card, .summary-card, .kpi-card, or .posture-card elements.
Components:
shared/components/stella-metric-card/stella-metric-card.component.ts— individual cardshared/components/stella-metric-card/stella-metric-grid.component.ts— responsive grid wrapper
Usage:
<stella-metric-grid [columns]="3">
<stella-metric-card label="Risk Posture" [value]="riskLevel()" subtitle="6 findings in scope"
icon="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" route="/triage/artifacts" />
<stella-metric-card label="VEX Coverage" value="0%" subtitle="0/0 findings covered" />
</stella-metric-grid>
Design rules:
- Cards are uncolored — no severity/status color backgrounds
- Icon is mandatory (SVG path d, multi-path via
|||) - Subtitle is 5-10 words explaining the metric
- If
routeis set: card is clickable with hover lift + arrow - If no
route: static display, no hover effect
Horizontal Scroll Card Lane Pattern (Reusable)
A reusable pattern for displaying actionable items as horizontally scrollable cards with gradient fades and scroll arrows. Used in the dashboard (environment cards, pending actions) and the approvals inbox (approval cards with inline actions).
Architecture
Three layers:
- Wrapper (
*-lane-wrapper) — relative-positioned container with::before/::aftergradient pseudo-elements - Scroll container (
*-lane) — flex row withoverflow-x: auto, hidden scrollbar,scroll-behavior: smooth - Cards — fixed-width (
280px) flex items withflex-shrink: 0
Scroll arrow signals (TypeScript)
@ViewChild('myScroll') myScrollRef?: ElementRef<HTMLDivElement>;
readonly showLeftArrow = signal(false);
readonly showRightArrow = signal(false);
onScroll(): void { this.updateArrows(); }
scrollCards(direction: 'left' | 'right'): void {
this.myScrollRef?.nativeElement?.scrollBy({ left: direction === 'left' ? -300 : 300, behavior: 'smooth' });
}
private updateArrows(): void {
const el = this.myScrollRef?.nativeElement;
if (!el) { this.showLeftArrow.set(false); this.showRightArrow.set(false); return; }
this.showLeftArrow.set(el.scrollLeft > 1);
this.showRightArrow.set(el.scrollWidth - el.scrollLeft - el.clientWidth > 1);
}
Gradient fades (CSS)
.my-wrapper.can-scroll-left::before {
content: ''; position: absolute; top: 0; left: 0; bottom: 0; width: 56px;
background: linear-gradient(to right, var(--color-surface-primary) 0%, transparent 100%);
pointer-events: none; z-index: 1;
}
.my-wrapper.can-scroll-right::after { /* mirror for right side */ }
Confirmation dialogs for card actions
- Production approve:
<app-confirm-dialog>withvariant="warning"andViewChildref, call.open()programmatically - Reject with reason: Custom inline dialog overlay with
<textarea [(ngModel)]>for optional reason - Detail popup:
<app-modal size="lg">— shows summary immediately, loads full detail via API on open
Reference implementations
- Dashboard environment cards:
features/dashboard-v3/dashboard-v3.component.ts(.env-grid-wrapper) - Approvals inbox cards:
features/approvals/approvals-inbox.component.ts(.cards-lane-wrapper,.apccards) - Stella Action Card List:
shared/components/stella-action-card/(simpler variant without arrows)
Tab Navigation Convention (MANDATORY)
All page-level tab navigation must use <stella-page-tabs>.
Do NOT create custom .tabs, .tab-navigation, .tab-button, or nav[role="tablist"] elements.
Do NOT use the app-tabs / TabsComponent for page tabs (that component is for inline content tabs only).
Component: shared/components/stella-page-tabs/stella-page-tabs.component.ts
Usage (signal-based, no router):
<stella-page-tabs
[tabs]="tabs"
[activeTab]="activeTab()"
(tabChange)="activeTab.set($any($event))"
ariaLabel="Section tabs"
>
@switch (activeTab()) {
@case ('first') { <my-first-panel /> }
@case ('second') { <my-second-panel /> }
}
</stella-page-tabs>
Usage (router-based):
<stella-page-tabs
[tabs]="pageTabs"
[activeTab]="activeTab()"
ariaLabel="Page tabs"
(tabChange)="onTabChange($event)"
>
<router-outlet></router-outlet>
</stella-page-tabs>
Tab definition:
const MY_TABS: readonly StellaPageTab[] = [
{ id: 'overview', label: 'Overview', icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z' },
{ id: 'details', label: 'Details', icon: 'M14 2H6a2...', status: 'warn', statusHint: '3 issues' },
];
Design rules:
- Every tab MUST have an SVG icon (
iconfield — SVG pathdattribute, multi-path via|||) - Labels should be short (1-2 words)
- Use
statusfor warn/error indicators,badgefor counts - Do NOT duplicate the tab bar styling — the component owns all tab CSS
- The component provides: keyboard navigation, ARIA roles, active background, bottom border, icon opacity transitions, panel border-radius, enter animation
Data Table Convention (MANDATORY)
All data tables must use <app-data-table> for interactive tables or .stella-table CSS classes for simple static tables.
Do NOT create custom table styling, sort headers, or selection checkboxes.
Components:
shared/components/data-table/data-table.component.ts— interactive table (sorting, selection, templates)shared/components/pagination/pagination.component.ts— page navigationsrc/styles/_tables.scss— global.stella-tableCSS classes
Usage (interactive table with sorting + pagination):
<app-data-table
[columns]="columns"
[data]="pagedItems()"
[loading]="loading()"
[striped]="true"
[hoverable]="true"
[selectable]="false"
emptyTitle="No items found"
emptyDescription="Adjust filters or create a new item."
(sortChange)="onSortChange($event)"
(rowClick)="onRowClick($event)"
>
<div tablePagination>
<app-pagination
[total]="totalItems()"
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[pageSizes]="[5, 10, 25, 50]"
(pageChange)="onPageChange($event)"
/>
</div>
</app-data-table>
Column definition with custom cell templates:
readonly columns: TableColumn<MyItem>[] = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'status', label: 'Status', sortable: true, template: statusTemplate },
{ key: 'actions', label: 'Actions', sortable: false, align: 'right' },
];
Usage (simple static table):
<table class="stella-table stella-table--striped stella-table--hoverable stella-table--bordered">
<thead><tr><th>Name</th><th>Value</th></tr></thead>
<tbody><tr><td>...</td><td>...</td></tr></tbody>
</table>
Design rules:
- Pagination must be right-aligned below the table
- Page size options must include 5:
[5, 10, 25, 50] - All list/catalog pages must be sortable by at least the primary column
- Use
stella-table--borderedfor tables that are the main page content - Use
stella-table--stripedfor tables with more than 5 rows - Loading state must show skeleton rows, not a spinner
Filter Convention (MANDATORY)
Three filter component types:
stella-filter-chip— Single-select dropdown (Region, Env, Stage, Type, Gate, Risk)stella-filter-multi— Multi-select with checkboxes + All/None (Severity, Status)stella-view-mode-switcher— Binary toggle (Operator/Auditor, view modes)
Components:
shared/components/stella-filter-chip/stella-filter-chip.component.ts— single-selectshared/components/stella-filter-multi/stella-filter-multi.component.ts— multi-selectshared/components/view-mode-switcher/view-mode-switcher.component.ts— binary toggleshared/ui/filter-bar/filter-bar.component.ts— combined search + dropdown filters + active chips
Usage (filter chips for page-level filters):
<stella-filter-chip
label="Status"
[value]="statusFilter()"
[options]="statusOptions"
(valueChange)="statusFilter.set($event)"
/>
<stella-filter-multi
label="Severity"
[options]="severityOptions()"
(optionsChange)="onSeverityChange($event)"
/>
Usage (filter bar with search + dropdowns):
<app-filter-bar
searchPlaceholder="Search..."
[filters]="filterOptions"
[activeFilters]="activeFilters()"
(searchChange)="searchQuery.set($event)"
(filterChange)="onFilterAdded($event)"
(filterRemove)="onFilterRemoved($event)"
(filtersCleared)="clearAllFilters()"
/>
Design rules:
- Global filters (Region, Env, Window, Stage, Operator/Auditor) live in the header bar only
- Pages must NOT duplicate global filters — read from PlatformContextStore
- Page-level filters use
stella-filter-chiporstella-filter-multiinline above the table - Use
app-filter-barwhen search + multiple dropdowns + active chips are needed - Compact inline chips: 28px height, no border default, dropdown on click
Filter container overflow (CRITICAL):
Filter containers that hold stella-filter-chip or stella-filter-multi MUST use
overflow: visible so dropdown panels are not clipped. Do NOT use overflow-x: auto
or overflow: hidden on filter row containers — this clips the absolute-positioned
dropdown panels (z-index: 200) below the fold.
/* CORRECT — dropdowns escape the container */
.filters { display: flex; flex-wrap: wrap; overflow: visible; gap: 0.5rem; }
/* WRONG — clips dropdown panels */
.filters { display: flex; flex-wrap: nowrap; overflow-x: auto; }
The topbar header row uses overflow: visible for this reason — all page-level
filter rows must follow the same pattern.
Filter Convention (MANDATORY)
Three filter component types:
stella-filter-chip— Single-select dropdown (Region, Env, Stage, Type, Gate, Risk)stella-filter-multi— Multi-select with checkboxes + All/None (Severity, Status)stella-view-mode-switcher— Binary toggle (Operator/Auditor, view modes)
Global filters (Region, Env, Window, Stage, Operator/Auditor) live in the header bar only. Pages must NOT duplicate global filters. Read from PlatformContextStore.
Design: Compact inline chips, 28px height, no border default, dropdown on click.