Files
git.stella-ops.org/src/Web/StellaOps.Web/AGENTS.md
master 95357ffbb9 Web UI: feature updates across all modules
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>
2026-03-27 12:28:48 +02:00

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-test builder (Jasmine compatibility shim in src/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/branding and 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, line
  • CompressedPath - Compact path representation
  • DriftedSink - Sink with reachability change and cause
  • DriftCause - 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.md
  • docs/technical/architecture/console-admin-rbac.md
  • docs/technical/architecture/console-branding.md

Working Agreement

    1. Update task status to DOING/DONE in both correspoding sprint file /docs/implplan/SPRINT_*.md when you start or finish work.
    1. Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
    1. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
    1. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
    1. Revert to TODO if you pause the task without shipping changes; leave notes in commit/PR descriptions for context.

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 card
  • shared/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 route is 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:

  1. Wrapper (*-lane-wrapper) — relative-positioned container with ::before/::after gradient pseudo-elements
  2. Scroll container (*-lane) — flex row with overflow-x: auto, hidden scrollbar, scroll-behavior: smooth
  3. Cards — fixed-width (280px) flex items with flex-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> with variant="warning" and ViewChild ref, 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, .apc cards)
  • 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 (icon field — SVG path d attribute, multi-path via |||)
  • Labels should be short (1-2 words)
  • Use status for warn/error indicators, badge for 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 navigation
  • src/styles/_tables.scss — global .stella-table CSS 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--bordered for tables that are the main page content
  • Use stella-table--striped for tables with more than 5 rows
  • Loading state must show skeleton rows, not a spinner

Filter Convention (MANDATORY)

Three filter component types:

  1. stella-filter-chip — Single-select dropdown (Region, Env, Stage, Type, Gate, Risk)
  2. stella-filter-multi — Multi-select with checkboxes + All/None (Severity, Status)
  3. stella-view-mode-switcher — Binary toggle (Operator/Auditor, view modes)

Components:

  • shared/components/stella-filter-chip/stella-filter-chip.component.ts — single-select
  • shared/components/stella-filter-multi/stella-filter-multi.component.ts — multi-select
  • shared/components/view-mode-switcher/view-mode-switcher.component.ts — binary toggle
  • shared/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-chip or stella-filter-multi inline above the table
  • Use app-filter-bar when 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:

  1. stella-filter-chip — Single-select dropdown (Region, Env, Stage, Type, Gate, Risk)
  2. stella-filter-multi — Multi-select with checkboxes + All/None (Severity, Status)
  3. 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.