diff --git a/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.spec.ts b/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.spec.ts new file mode 100644 index 000000000..42c176478 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.spec.ts @@ -0,0 +1,129 @@ +import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppConfigService } from '../config/app-config.service'; +import { AuthHttpInterceptor } from '../auth/auth-http.interceptor'; +import { AuthorityAuthService } from '../auth/authority-auth.service'; +import { DpopService } from '../auth/dpop/dpop.service'; +import { TenantActivationService } from '../auth/tenant-activation.service'; +import { AUDIT_BUNDLES_API_BASE_URL, AuditBundlesHttpClient } from './audit-bundles.client'; + +describe('AuditBundlesHttpClient', () => { + let client: AuditBundlesHttpClient; + let httpMock: HttpTestingController; + let auth: Pick & { + getAuthHeadersForRequest: jasmine.Spy; + }; + + beforeEach(() => { + auth = { + getAuthHeadersForRequest: jasmine + .createSpy('getAuthHeadersForRequest') + .and.returnValue( + Promise.resolve({ + authorization: 'DPoP access-token', + dpop: 'proof-token', + }) + ), + }; + + TestBed.configureTestingModule({ + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + AuditBundlesHttpClient, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthHttpInterceptor, + multi: true, + }, + { + provide: AuthorityAuthService, + useValue: auth, + }, + { + provide: AppConfigService, + useValue: { + authority: { + issuer: 'https://stella-ops.local', + tokenEndpoint: '/connect/token', + authorizeEndpoint: '/connect/authorize', + }, + }, + }, + { + provide: DpopService, + useValue: { + setNonce: jasmine.createSpy('setNonce').and.returnValue(Promise.resolve()), + }, + }, + { + provide: TenantActivationService, + useValue: { + activeTenantId: () => 'demo-prod', + }, + }, + { + provide: AUDIT_BUNDLES_API_BASE_URL, + useValue: '', + }, + ], + }); + + client = TestBed.inject(AuditBundlesHttpClient); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('leaves auth to the shared interceptor so audit bundle creation uses DPoP headers', async () => { + const responsePromise = firstValueFrom(client.createBundle({ + subject: { + type: 'IMAGE', + name: 'asset-web-prod', + digest: { sha256: 'sha256:test-bundle-probe' }, + }, + contents: { + vulnReports: true, + sbom: true, + vex: true, + policyEvals: true, + attestations: true, + }, + })); + + await Promise.resolve(); + + const request = httpMock.expectOne((pending) => pending.url === '/v1/audit-bundles'); + expect(auth.getAuthHeadersForRequest).toHaveBeenCalledOnceWith( + `${window.location.origin}/v1/audit-bundles`, + 'POST' + ); + expect(request.request.headers.get('Authorization')).toBe('DPoP access-token'); + expect(request.request.headers.get('DPoP')).toBe('proof-token'); + expect(request.request.headers.get('X-Stella-Tenant')).toBe('demo-prod'); + request.flush({ + bundleId: 'bndl-0001', + status: 'queued', + statusUrl: '/v1/audit-bundles/bndl-0001', + }); + await responsePromise; + }); + + it('uses the interceptor for audit bundle downloads too', async () => { + const responsePromise = firstValueFrom(client.downloadBundle('bndl-0001')); + + await Promise.resolve(); + + const request = httpMock.expectOne((pending) => pending.url === '/v1/audit-bundles/bndl-0001/download'); + expect(request.request.headers.get('Authorization')).toBe('DPoP access-token'); + expect(request.request.headers.get('DPoP')).toBe('proof-token'); + expect(request.request.headers.get('Accept')).toBe('application/zip'); + request.flush(new Blob(['bundle'], { type: 'application/zip' })); + await responsePromise; + }); +}); diff --git a/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts b/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts index e40d66cb1..2eda2cbaa 100644 --- a/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts +++ b/src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts @@ -2,7 +2,6 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Inject, Injectable, InjectionToken, inject } from '@angular/core'; import { Observable, of, delay, map, catchError, throwError } from 'rxjs'; -import { AuthSessionStore } from '../auth/auth-session.store'; import { TenantActivationService } from '../auth/tenant-activation.service'; import { generateTraceId } from './trace.util'; import type { @@ -82,7 +81,6 @@ export class AuditBundlesHttpClient implements AuditBundlesApi { constructor( private readonly http: HttpClient, - private readonly authSession: AuthSessionStore, @Inject(AUDIT_BUNDLES_API_BASE_URL) private readonly baseUrl: string ) {} @@ -210,11 +208,6 @@ export class AuditBundlesHttpClient implements AuditBundlesApi { Accept: 'application/json', }); - const accessToken = this.authSession.session()?.tokens.accessToken; - if (accessToken) { - headers = headers.set('Authorization', `Bearer ${accessToken}`); - } - if (projectId) headers = headers.set('X-Stella-Project', projectId); return headers; diff --git a/src/Web/StellaOps.Web/src/app/features/offline-kit/components/bundle-management.component.ts b/src/Web/StellaOps.Web/src/app/features/offline-kit/components/bundle-management.component.ts index 057f72212..773a9546d 100644 --- a/src/Web/StellaOps.Web/src/app/features/offline-kit/components/bundle-management.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/offline-kit/components/bundle-management.component.ts @@ -138,7 +138,7 @@ interface LoadedBundle { .management-header h2 { font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-heading); margin: 0 0 0.25rem; } @@ -161,8 +161,8 @@ interface LoadedBundle { } .section-card { - background: rgba(30, 41, 59, 0.4); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-secondary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.5rem; } @@ -170,7 +170,7 @@ interface LoadedBundle { .section-card h3 { font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-heading); margin: 0 0 1rem; } @@ -187,8 +187,8 @@ interface LoadedBundle { } .bundle-card { - background: rgba(15, 23, 42, 0.5); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -258,7 +258,7 @@ interface LoadedBundle { .meta-value { font-size: 0.875rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .bundle-actions { @@ -273,8 +273,8 @@ interface LoadedBundle { } .category-card { - background: rgba(15, 23, 42, 0.5); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -297,7 +297,7 @@ interface LoadedBundle { .category-name { font-weight: var(--font-weight-medium); - color: var(--color-border-primary); + color: var(--color-text-heading); } .category-count { @@ -316,7 +316,7 @@ interface LoadedBundle { justify-content: space-between; align-items: center; padding: 0.375rem 0.5rem; - background: rgba(30, 41, 59, 0.5); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); font-size: 0.75rem; } @@ -353,8 +353,8 @@ interface LoadedBundle { } .btn--secondary { - background: var(--color-text-primary); - color: var(--color-border-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); } .btn--danger { 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 cd2073a32..4065b3df7 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 @@ -73,7 +73,7 @@ import { OfflineModeService } from '../../core/services/offline-mode.service'; styles: [` .offline-kit-layout { min-height: 100%; - background: var(--color-text-heading); + background: var(--color-surface-primary); } .page-header { @@ -81,13 +81,13 @@ import { OfflineModeService } from '../../core/services/offline-mode.service'; justify-content: space-between; align-items: flex-start; padding: 1.5rem 2rem; - border-bottom: 1px solid var(--color-text-primary); + border-bottom: 1px solid var(--color-border-primary); } .header-content h1 { font-size: 1.5rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-tertiary); + color: var(--color-text-heading); margin: 0 0 0.25rem; } @@ -105,7 +105,7 @@ import { OfflineModeService } from '../../core/services/offline-mode.service'; } .page-shortcuts a { - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-full); color: var(--color-text-muted); font-size: 0.72rem; @@ -114,8 +114,8 @@ import { OfflineModeService } from '../../core/services/offline-mode.service'; } .page-shortcuts a:hover { - color: var(--color-border-primary); - border-color: var(--color-border-primary); + color: var(--color-text-link); + border-color: var(--color-text-link); } .connection-status { @@ -159,8 +159,8 @@ import { OfflineModeService } from '../../core/services/offline-mode.service'; display: flex; gap: 0.25rem; padding: 0 2rem; - background: var(--color-text-heading); - border-bottom: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border-bottom: 1px solid var(--color-border-primary); } .tab-link { diff --git a/src/Web/StellaOps.Web/src/app/features/policy-studio/workspace/policy-workspace.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-studio/workspace/policy-workspace.component.ts index 21d8ccd24..14b5a3f4f 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-studio/workspace/policy-workspace.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-studio/workspace/policy-workspace.component.ts @@ -103,29 +103,29 @@ import { PolicyPackStore } from '../services/policy-pack.store'; `, styles: [ ` - :host { display: block; background: var(--color-surface-inverse); color: var(--color-border-primary); min-height: 100vh; } + :host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); min-height: 100vh; } .workspace { max-width: 1200px; margin: 0 auto; padding: 1.5rem; } .workspace__header { margin-bottom: 1rem; } .workspace__eyebrow { margin: 0; color: var(--color-status-info); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.8rem; } .workspace__lede { margin: 0.2rem 0 0; color: var(--color-text-muted); } .workspace__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1rem; } - .pack-card { background: var(--color-text-heading); border: 1px solid var(--color-text-heading); border-radius: var(--radius-xl); padding: 1rem; box-shadow: 0 12px 30px rgba(0,0,0,0.28); display: grid; gap: 0.6rem; } + .pack-card { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; box-shadow: var(--shadow-md); display: grid; gap: 0.6rem; } .pack-card__head { display: flex; justify-content: space-between; gap: 0.75rem; align-items: flex-start; } - .pack-card__eyebrow { margin: 0; color: var(--color-brand-muted); font-size: 0.75rem; letter-spacing: 0.05em; text-transform: uppercase; } - .pack-card__desc { margin: 0.2rem 0 0; color: rgba(212, 201, 168, 0.5); } + .pack-card__eyebrow { margin: 0; color: var(--color-brand-secondary); font-size: 0.75rem; letter-spacing: 0.05em; text-transform: uppercase; } + .pack-card__desc { margin: 0.2rem 0 0; color: var(--color-text-muted); } .pack-card__meta { display: grid; justify-items: end; gap: 0.2rem; color: var(--color-text-muted); font-size: 0.9rem; } .pack-card__tags { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 0.35rem; } - .pack-card__tags li { padding: 0.2rem 0.45rem; border: 1px solid var(--color-text-heading); border-radius: var(--radius-full); background: var(--color-surface-inverse); } + .pack-card__tags li { padding: 0.2rem 0.45rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-full); background: var(--color-surface-tertiary); } .pack-card__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; } - .pack-card__actions a { color: var(--color-border-primary); border: 1px solid var(--color-text-primary); border-radius: var(--radius-lg); padding: 0.35rem 0.6rem; text-decoration: none; } + .pack-card__actions a { color: var(--color-text-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 0.35rem 0.6rem; text-decoration: none; } .pack-card__actions a:hover { border-color: var(--color-status-info); } .pack-card__actions a.action-disabled { opacity: 0.5; pointer-events: none; border-style: dashed; } .pack-card__detail { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.35rem 1rem; margin: 0; } dt { color: var(--color-text-muted); font-size: 0.85rem; margin: 0; } - dd { margin: 0; color: var(--color-border-primary); } - .workspace__banner { background: var(--color-text-heading); border: 1px solid var(--color-text-primary); color: var(--color-status-warning-border); padding: 0.75rem 1rem; border-radius: var(--radius-xl); margin: 0.5rem 0 1rem; } + dd { margin: 0; color: var(--color-text-primary); } + .workspace__banner { background: var(--color-status-warning-bg); border: 1px solid var(--color-status-warning-border); color: var(--color-status-warning-text); padding: 0.75rem 1rem; border-radius: var(--radius-xl); margin: 0.5rem 0 1rem; } .workspace__footer { margin-top: 0.8rem; } - .workspace__footer button { background: var(--color-status-info-text); border: 1px solid var(--color-status-info-text); color: var(--color-border-primary); border-radius: var(--radius-lg); padding: 0.45rem 0.8rem; } + .workspace__footer button { background: var(--color-brand-primary); border: 1px solid var(--color-brand-primary); color: var(--color-text-inverse); border-radius: var(--radius-lg); padding: 0.45rem 0.8rem; cursor: pointer; } `, ] }) diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/airgap-audit.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/airgap-audit.component.ts index cfff1a59b..81121f8dc 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/airgap-audit.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/airgap-audit.component.ts @@ -313,7 +313,7 @@ export type AirgapEventType = } .status-info strong { - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 1.1rem; } @@ -344,15 +344,15 @@ export type AirgapEventType = flex-direction: column; align-items: center; padding: 1rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } .stat-value { font-size: 1.5rem; font-weight: var(--font-weight-bold); - color: var(--color-border-primary); + color: var(--color-text-primary); font-variant-numeric: tabular-nums; } @@ -384,10 +384,10 @@ export type AirgapEventType = .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 160px; } @@ -430,8 +430,8 @@ export type AirgapEventType = } .event-card { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; cursor: pointer; @@ -439,7 +439,7 @@ export type AirgapEventType = } .event-card:hover { - border-color: var(--color-text-primary); + border-color: var(--color-border-primary); } .event-card--warning { @@ -555,7 +555,7 @@ export type AirgapEventType = .event-card__details { margin-top: 1rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .event-card__details h4 { @@ -567,10 +567,10 @@ export type AirgapEventType = .details-json { margin: 0; padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-md); font-size: 0.8rem; - color: var(--color-border-primary); + color: var(--color-text-primary); overflow-x: auto; } @@ -581,13 +581,13 @@ export type AirgapEventType = gap: 1rem; padding: 1rem; margin-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .airgap-audit__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/certificate-inventory.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/certificate-inventory.component.ts index 66c1309d2..8bb6c4381 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/certificate-inventory.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/certificate-inventory.component.ts @@ -506,7 +506,7 @@ import { align-items: center; gap: 1rem; padding: 0.5rem 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); } @@ -522,7 +522,7 @@ import { .alert-name { font-weight: var(--font-weight-medium); - color: var(--color-border-primary); + color: var(--color-text-primary); } .alert-type { @@ -578,10 +578,10 @@ import { .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 160px; } @@ -630,11 +630,11 @@ import { .cert-table td { padding: 0.75rem 1rem; text-align: left; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .cert-table th { - background: var(--color-surface-inverse); + background: var(--color-surface-primary); color: var(--color-text-muted); font-weight: var(--font-weight-medium); font-size: 0.85rem; @@ -679,7 +679,7 @@ import { } .cert-name strong { - color: var(--color-border-primary); + color: var(--color-text-primary); display: flex; align-items: center; gap: 0.5rem; @@ -720,7 +720,7 @@ import { } .subject-cn { - color: var(--color-border-primary); + color: var(--color-text-primary); font-family: monospace; font-size: 0.85rem; } @@ -767,7 +767,7 @@ import { .btn-chain { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-status-info); border-radius: var(--radius-sm); padding: 0.15rem 0.4rem; @@ -785,8 +785,8 @@ import { .btn-action { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-sm); padding: 0.25rem 0.5rem; font-size: 0.8rem; @@ -806,13 +806,13 @@ import { align-items: center; gap: 1rem; padding: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .cert-inventory__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; @@ -845,8 +845,8 @@ import { .detail-modal, .chain-modal { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); width: 90%; max-width: 600px; @@ -861,7 +861,7 @@ import { align-items: center; gap: 1rem; padding: 1rem 1.5rem; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .detail-modal__header h3, @@ -872,7 +872,7 @@ import { .btn-close { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-text-muted); width: 28px; height: 28px; @@ -927,7 +927,7 @@ import { .detail-item dd { margin: 0; - color: var(--color-border-primary); + color: var(--color-text-primary); } .mono { @@ -965,7 +965,7 @@ import { .san-list li { font-family: monospace; font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .detail-modal__footer, @@ -974,13 +974,13 @@ import { justify-content: flex-end; gap: 0.5rem; padding: 1rem 1.5rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .btn-secondary { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; @@ -1016,8 +1016,8 @@ import { gap: 1rem; width: 100%; padding: 0.75rem 1rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -1035,10 +1035,10 @@ import { display: flex; align-items: center; justify-content: center; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-md); font-weight: var(--font-weight-bold); - color: var(--color-border-primary); + color: var(--color-text-primary); } .chain-node--root .chain-node__icon { @@ -1060,7 +1060,7 @@ import { .chain-node__name { font-weight: var(--font-weight-medium); - color: var(--color-border-primary); + color: var(--color-text-primary); } .chain-node__cn { diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/incident-audit.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/incident-audit.component.ts index 3f77acb9e..d2060112a 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/incident-audit.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/incident-audit.component.ts @@ -329,15 +329,15 @@ export type IncidentType = flex-direction: column; align-items: center; padding: 1rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); cursor: pointer; transition: all 0.15s; } .summary-card:hover { - border-color: var(--color-text-primary); + border-color: var(--color-border-primary); } .summary-card--open { border-left: 3px solid var(--color-status-error); } @@ -349,7 +349,7 @@ export type IncidentType = .summary-value { font-size: 1.75rem; font-weight: var(--font-weight-bold); - color: var(--color-border-primary); + color: var(--color-text-primary); font-variant-numeric: tabular-nums; } @@ -381,10 +381,10 @@ export type IncidentType = .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 160px; } @@ -427,15 +427,15 @@ export type IncidentType = } .incident-card { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); overflow: hidden; transition: border-color 0.15s; } .incident-card:hover { - border-color: var(--color-text-primary); + border-color: var(--color-border-primary); } .incident-card--critical { @@ -489,7 +489,7 @@ export type IncidentType = margin: 0; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-primary); } .incident-id { @@ -549,9 +549,9 @@ export type IncidentType = } .incident-card__details { - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); padding: 1.25rem; - background: rgba(15, 23, 42, 0.5); + background: var(--color-surface-secondary); } .details-section { @@ -581,7 +581,7 @@ export type IncidentType = align-items: center; gap: 0.75rem; padding: 0.5rem 0.75rem; - background: var(--color-surface-inverse); + background: var(--color-surface-primary); border-radius: var(--radius-md); } @@ -597,7 +597,7 @@ export type IncidentType = .resource-name { flex: 1; - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; } @@ -638,14 +638,14 @@ export type IncidentType = top: 16px; bottom: 0; width: 2px; - background: var(--color-text-heading); + background: var(--color-border-primary); } .timeline-marker { width: 12px; height: 12px; border-radius: var(--radius-full); - background: var(--color-text-primary); + background: var(--color-border-primary); flex-shrink: 0; margin-top: 4px; } @@ -669,7 +669,7 @@ export type IncidentType = .timeline-type { font-size: 0.75rem; font-weight: var(--font-weight-medium); - color: var(--color-border-primary); + color: var(--color-text-primary); } .timeline-time { @@ -691,10 +691,10 @@ export type IncidentType = .details-json { margin: 0; padding: 0.75rem; - background: var(--color-surface-inverse); + background: var(--color-surface-primary); border-radius: var(--radius-md); font-size: 0.8rem; - color: var(--color-border-primary); + color: var(--color-text-primary); overflow-x: auto; } @@ -703,13 +703,13 @@ export type IncidentType = gap: 0.5rem; margin-top: 1rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .btn-action { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.4rem 0.75rem; font-size: 0.85rem; @@ -729,13 +729,13 @@ export type IncidentType = gap: 1rem; padding: 1rem; margin-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .incident-audit__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/issuer-trust-list.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/issuer-trust-list.component.ts index 6222d5d4a..87df9163f 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/issuer-trust-list.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/issuer-trust-list.component.ts @@ -289,10 +289,10 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 160px; } @@ -335,7 +335,7 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; gap: 1.5rem; margin-bottom: 1.5rem; padding: 1rem; - background: var(--color-surface-inverse); + background: var(--color-surface-primary); border-radius: var(--radius-lg); } @@ -386,11 +386,11 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; .issuer-table td { padding: 0.75rem 1rem; text-align: left; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .issuer-table th { - background: var(--color-surface-inverse); + background: var(--color-surface-primary); color: var(--color-text-muted); font-weight: var(--font-weight-medium); font-size: 0.85rem; @@ -429,7 +429,7 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; } .issuer-info strong { - color: var(--color-border-primary); + color: var(--color-text-primary); } .issuer-name { @@ -466,7 +466,7 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; .score-bar { width: 60px; height: 6px; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); overflow: hidden; } @@ -517,8 +517,8 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; .btn-action { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-sm); padding: 0.25rem 0.5rem; font-size: 0.8rem; @@ -548,13 +548,13 @@ import { TrustScoreConfigComponent } from './trust-score-config.component'; align-items: center; gap: 1rem; padding: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .issuer-list__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-detail-panel.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-detail-panel.component.ts index 2517d8380..1876523c3 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-detail-panel.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-detail-panel.component.ts @@ -273,8 +273,8 @@ import { bottom: 0; width: 480px; max-width: 100vw; - background: var(--color-text-heading); - border-left: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border-left: 1px solid var(--color-border-primary); display: flex; flex-direction: column; z-index: 1000; @@ -286,7 +286,7 @@ import { justify-content: space-between; align-items: flex-start; padding: 1.5rem; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .detail-panel__header h2 { @@ -303,7 +303,7 @@ import { .btn-close { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-text-muted); width: 32px; height: 32px; @@ -356,7 +356,7 @@ import { .detail-item dd { margin: 0; - color: var(--color-border-primary); + color: var(--color-text-primary); } .fingerprint { @@ -428,7 +428,7 @@ import { .breakdown-bar { display: flex; height: 8px; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); overflow: hidden; } @@ -479,8 +479,8 @@ import { .history-item { padding: 0.75rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); margin-bottom: 0.5rem; } @@ -520,8 +520,8 @@ import { display: flex; flex-direction: column; padding: 0.5rem 0.75rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); } @@ -558,7 +558,7 @@ import { display: flex; gap: 0.5rem; padding: 0.35rem 0; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .metadata-item dt { @@ -568,7 +568,7 @@ import { .metadata-item dd { margin: 0; - color: var(--color-border-primary); + color: var(--color-text-primary); font-family: monospace; font-size: 0.85rem; } @@ -577,7 +577,7 @@ import { display: flex; gap: 0.5rem; padding: 1rem 1.5rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .btn-primary, @@ -602,8 +602,8 @@ import { .btn-secondary { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); } .btn-secondary:hover { @@ -612,7 +612,7 @@ import { .btn-danger { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-status-error); } diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-expiry-warning.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-expiry-warning.component.ts index 6ada30f48..428ab94a9 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-expiry-warning.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-expiry-warning.component.ts @@ -134,7 +134,7 @@ import { KeyExpiryAlert } from '../../core/api/trust.models'; margin: 0; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-primary); } .expiry-warnings__title p { @@ -145,8 +145,8 @@ import { KeyExpiryAlert } from '../../core/api/trust.models'; .btn-toggle { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; @@ -172,8 +172,8 @@ import { KeyExpiryAlert } from '../../core/api/trust.models'; } .alert-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -199,7 +199,7 @@ import { KeyExpiryAlert } from '../../core/api/trust.models'; .alert-card__name { font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-primary); } .purpose-badge { @@ -245,7 +245,7 @@ import { KeyExpiryAlert } from '../../core/api/trust.models'; align-items: center; gap: 1rem; padding-top: 0.75rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .suggested-action { diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-rotation-wizard.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-rotation-wizard.component.ts index 6fd4674cc..1fcce47a0 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/key-rotation-wizard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/key-rotation-wizard.component.ts @@ -334,8 +334,8 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp } .wizard-modal { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-2xl); width: 90%; max-width: 640px; @@ -349,7 +349,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp justify-content: space-between; align-items: center; padding: 1.25rem 1.5rem; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .wizard-header h2 { @@ -360,7 +360,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .btn-close { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-text-muted); width: 32px; height: 32px; @@ -400,7 +400,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp display: flex; align-items: center; justify-content: center; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); color: var(--color-text-secondary); font-weight: var(--font-weight-semibold); transition: all 0.2s; @@ -432,7 +432,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .progress-connector { width: 40px; height: 2px; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); transition: background-color 0.2s; } @@ -467,8 +467,8 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp } .key-summary { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -491,7 +491,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp } .summary-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .summary-value.mono { @@ -573,16 +573,16 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .form-group label { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .form-group input, .form-group select, .form-group textarea { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.6rem 0.75rem; font-size: 0.9rem; } @@ -638,7 +638,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp } .confirm-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .confirm-value.mono { @@ -668,7 +668,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .rotating-spinner { width: 60px; height: 60px; - border: 4px solid var(--color-text-heading); + border: 4px solid var(--color-surface-tertiary); border-top-color: var(--color-status-info); border-radius: var(--radius-full); animation: spin 1s linear infinite; @@ -722,8 +722,8 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .new-key-info { margin-top: 1.5rem; padding: 1rem; - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); text-align: left; width: 100%; @@ -741,7 +741,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp } .new-key-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .new-key-value.mono { @@ -764,7 +764,7 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp justify-content: flex-end; gap: 0.75rem; padding: 1rem 1.5rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .btn-primary, @@ -794,8 +794,8 @@ export type WizardStep = 'review' | 'configure' | 'confirm' | 'rotating' | 'comp .btn-secondary { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); } .btn-secondary:hover { diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/signing-key-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/signing-key-dashboard.component.ts index 23aa623cf..08764a443 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/signing-key-dashboard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/signing-key-dashboard.component.ts @@ -288,10 +288,10 @@ import { KeyRotationWizardComponent } from './key-rotation-wizard.component'; .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 180px; } @@ -340,11 +340,11 @@ import { KeyRotationWizardComponent } from './key-rotation-wizard.component'; .key-table td { padding: 0.75rem 1rem; text-align: left; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .key-table th { - background: var(--color-surface-inverse); + background: var(--color-surface-primary); color: var(--color-text-muted); font-weight: var(--font-weight-medium); font-size: 0.85rem; @@ -384,7 +384,7 @@ import { KeyRotationWizardComponent } from './key-rotation-wizard.component'; } .key-name strong { - color: var(--color-border-primary); + color: var(--color-text-primary); } .key-desc { @@ -493,8 +493,8 @@ import { KeyRotationWizardComponent } from './key-rotation-wizard.component'; .btn-action { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-sm); padding: 0.25rem 0.5rem; font-size: 0.8rem; @@ -528,13 +528,13 @@ import { KeyRotationWizardComponent } from './key-rotation-wizard.component'; align-items: center; gap: 1rem; padding: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .key-dashboard__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-admin.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-admin.component.ts index d483091b9..ebf594f85 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-admin.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-admin.component.ts @@ -207,8 +207,8 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ styles: [` :host { display: block; - background: var(--color-surface-inverse); - color: var(--color-border-primary); + background: var(--color-surface-primary); + color: var(--color-text-primary); min-height: 100vh; } @@ -257,8 +257,8 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ .btn-secondary { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-lg); padding: 0.5rem 1rem; cursor: pointer; @@ -297,8 +297,8 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ display: flex; align-items: center; gap: 1rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; } @@ -358,7 +358,7 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ .trust-admin__tabs { display: flex; gap: 0.25rem; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); margin-bottom: 1.5rem; } @@ -374,7 +374,7 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ } .trust-admin__tab:hover { - color: var(--color-border-primary); + color: var(--color-text-primary); } .trust-admin__tab--active { @@ -405,8 +405,8 @@ const TRUST_ADMIN_TABS: readonly TrustAdminTab[] = [ } .trust-admin__content { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); min-height: 400px; } diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-audit-log.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-audit-log.component.ts index 28ed677b2..5f9e9917e 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-audit-log.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-audit-log.component.ts @@ -250,10 +250,10 @@ import { .filter-group input, .filter-group select { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.5rem 0.75rem; min-width: 140px; } @@ -315,8 +315,8 @@ import { } .event-card { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; cursor: pointer; @@ -324,7 +324,7 @@ import { } .event-card:hover { - border-color: var(--color-text-primary); + border-color: var(--color-border-primary); } .event-card--warning { @@ -368,7 +368,7 @@ import { .event-type { font-weight: var(--font-weight-medium); - color: var(--color-border-primary); + color: var(--color-text-primary); } .event-time { @@ -412,7 +412,7 @@ import { .event-card__details { margin-top: 1rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .event-details-grid { @@ -435,7 +435,7 @@ import { .detail-item dd { margin: 0; - color: var(--color-border-primary); + color: var(--color-text-primary); } .mono { @@ -464,10 +464,10 @@ import { .change-value { margin: 0; padding: 0.5rem; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); font-size: 0.8rem; - color: var(--color-border-primary); + color: var(--color-text-primary); overflow-x: auto; } @@ -485,10 +485,10 @@ import { .details-value { margin: 0; padding: 0.5rem; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); font-size: 0.8rem; - color: var(--color-border-primary); + color: var(--color-text-primary); overflow-x: auto; } @@ -499,13 +499,13 @@ import { gap: 1rem; padding: 1rem; margin-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .audit-log__pagination button { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); border-radius: var(--radius-md); padding: 0.5rem 1rem; cursor: pointer; diff --git a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-score-config.component.ts b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-score-config.component.ts index 4cf29ddc8..284bdd961 100644 --- a/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-score-config.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/trust-admin/trust-score-config.component.ts @@ -278,8 +278,8 @@ import { `, styles: [` .config-panel { - background: var(--color-surface-inverse); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); margin-bottom: 1.5rem; overflow: hidden; @@ -290,8 +290,8 @@ import { justify-content: space-between; align-items: center; padding: 1rem 1.5rem; - background: var(--color-text-heading); - border-bottom: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border-bottom: 1px solid var(--color-border-primary); } .config-panel__header h3 { @@ -302,7 +302,7 @@ import { .btn-close { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-text-muted); width: 28px; height: 28px; @@ -362,7 +362,7 @@ import { display: flex; justify-content: space-between; font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .weight-value { @@ -374,7 +374,7 @@ import { .weight-control input[type="range"] { width: 100%; height: 6px; - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); appearance: none; cursor: pointer; @@ -395,7 +395,7 @@ import { } .preview-section { - background: var(--color-text-heading); + background: var(--color-surface-tertiary); border-radius: var(--radius-lg); padding: 1rem; } @@ -432,7 +432,7 @@ import { } .preview-score--new { - color: var(--color-border-primary); + color: var(--color-text-primary); } .preview-score--new.score-up { @@ -486,7 +486,7 @@ import { .impact-value { font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-border-primary); + color: var(--color-text-primary); } .impact-label { @@ -513,10 +513,10 @@ import { } .threshold-control input[type="number"] { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-tertiary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); - color: var(--color-border-primary); + color: var(--color-text-primary); padding: 0.4rem 0.6rem; width: 100%; } @@ -555,8 +555,8 @@ import { justify-content: flex-end; gap: 0.5rem; padding: 1rem 1.5rem; - background: var(--color-text-heading); - border-top: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border-top: 1px solid var(--color-border-primary); } .btn-save, @@ -586,8 +586,8 @@ import { .btn-cancel { background: transparent; - border: 1px solid var(--color-text-primary); - color: var(--color-border-primary); + border: 1px solid var(--color-border-primary); + color: var(--color-text-primary); } .btn-cancel:hover { @@ -596,7 +596,7 @@ import { .btn-reset { background: transparent; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); color: var(--color-status-warning-border); margin-right: auto; } diff --git a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts index d74cb597c..7c0a34b74 100644 --- a/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/app-sidebar/app-sidebar.component.ts @@ -122,29 +122,15 @@ interface NavSectionGroup {
} @if (!effectiveCollapsed && section.displayChildren.length > 0) { - -
-
- - -
+ +
+
@for (child of section.displayChildren; track child.id) { @@ -247,7 +233,6 @@ interface NavSectionGroup { .sidebar--collapsed.sidebar--flyout .sb-group__header, .sidebar--collapsed.sidebar--flyout .sb-divider, - .sidebar--collapsed.sidebar--flyout .sb-section__head, .sidebar--collapsed.sidebar--flyout .sb-section__body { animation: flyoutFadeIn 0.2s ease-out both; } @@ -477,83 +462,15 @@ interface NavSectionGroup { margin: 0.25rem 0; } - /* Section head: nav-item + chevron */ - .sb-section__head { - position: relative; - display: flex; - align-items: center; - margin: 0; - padding: 0; - } - - .sb-section__head > app-sidebar-nav-item { - flex: 1; - min-width: 0; - } - - .sb-section__chevron { - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border: none; - border-radius: 5px; - background: transparent; - color: var(--color-sidebar-text-muted); - cursor: pointer; - opacity: 0.25; - transition: opacity 0.2s, background 0.2s, color 0.2s, transform 0.15s; - margin-right: 0.25rem; - - &:hover { - opacity: 1 !important; - background: rgba(245, 166, 35, 0.12); - color: var(--color-sidebar-active-text); - transform: scale(1.1); - } - - &:focus-visible { - opacity: 1 !important; - outline: 1.5px solid var(--color-sidebar-active-border); - outline-offset: -1.5px; - } - } - - .sb-section__head:hover .sb-section__chevron { - opacity: 0.7; - } - - .sb-section--folded .sb-section__chevron { - opacity: 0.45; - } - - .sb-section__chevron-icon { - transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); - } - - .sb-section--folded .sb-section__chevron-icon { - transform: rotate(-90deg); - } - - /* Animated section body */ + /* Section children */ .sb-section__body { - display: grid; - grid-template-rows: 1fr; - transition: grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1); - } - - .sb-section--folded .sb-section__body { - grid-template-rows: 0fr; + display: block; } .sb-section__body-inner { - overflow: hidden; margin-left: 1.375rem; padding-left: 0.375rem; position: relative; - transition: opacity 0.24s ease; &::before { content: ''; @@ -571,10 +488,6 @@ interface NavSectionGroup { } } - .sb-section--folded .sb-section__body-inner { - opacity: 0; - } - /* ================================================================ Footer ================================================================ */ @@ -780,7 +693,7 @@ export class AppSidebarComponent implements AfterViewInit { StellaOpsScopes.RELEASE_WRITE, StellaOpsScopes.RELEASE_PUBLISH, ], - }, + }, { id: 'rel-hotfix-list', label: 'Hotfixes', route: '/releases/hotfixes', icon: 'zap' }, { id: 'rel-envs', label: 'Environments', route: '/releases/environments', icon: 'globe' }, { diff --git a/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts b/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts index 62952623a..1d55b888e 100644 --- a/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts +++ b/src/Web/StellaOps.Web/src/app/layout/global-search/global-search.component.ts @@ -997,6 +997,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { readonly placeholderIndex = signal(0); readonly searchResponse = signal(null); readonly suggestionViability = signal(null); + readonly suggestionViabilityStatus = signal<'idle' | 'ready' | 'unavailable'>('idle'); readonly recentSearches = signal([]); readonly suppressedStarterQueries = signal([]); readonly expandedCardKey = signal(null); @@ -1243,6 +1244,29 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { }; } + if (this.isSearchEnvironmentLikelyUnready(response)) { + return { + status: 'insufficient', + eyebrow, + title: this.t('ui.search.answer.title.unready', 'Search needs live data'), + summary: this.t( + 'ui.search.answer.summary.unready', + 'The live search service accepted "{query}" but returned zero indexed matches. This usually means search ingestion or index rebuild is not ready for this environment.', + { query }, + ), + evidence: this.t( + 'ui.search.answer.evidence.unready', + 'Search suggestion verification is also unavailable on this environment, so starter queries cannot be trusted until the search APIs are fully configured.', + ), + citations: [], + questionLabel: '', + questions: [], + guidanceLabel: null, + guidance: [], + nextSearches: [], + }; + } + const clarifyingQuestions = this.clarifyingQuestions(); if (clarifyingQuestions.length > 0) { return { @@ -2106,6 +2130,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { if (queries.length === 0) { this.suggestionViability.set(null); + this.suggestionViabilityStatus.set('idle'); return; } @@ -2113,6 +2138,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe((response) => { this.suggestionViability.set(response); + this.suggestionViabilityStatus.set(response ? 'ready' : 'unavailable'); }); } @@ -2399,6 +2425,18 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { || (response.synthesis?.sourceCount ?? 0) > 0; } + private hasZeroIndexedMatches(response: UnifiedSearchResponse): boolean { + return !this.hasSearchEvidence(response) + && (response.diagnostics?.ftsMatches ?? 0) === 0 + && (response.diagnostics?.vectorMatches ?? 0) === 0 + && (response.diagnostics?.entityCardCount ?? 0) === 0; + } + + private isSearchEnvironmentLikelyUnready(response: UnifiedSearchResponse): boolean { + return this.suggestionViabilityStatus() === 'unavailable' + && this.hasZeroIndexedMatches(response); + } + private reconcileSuggestedExecution(response: UnifiedSearchResponse): void { const pending = this.pendingSuggestedExecution; if (!pending) { diff --git a/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts b/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts index 7f2a0f3b6..c77c96ee5 100644 --- a/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts +++ b/src/Web/StellaOps.Web/src/tests/global_search/global-search.component.spec.ts @@ -818,6 +818,25 @@ describe('GlobalSearchComponent', () => { expect(guidanceItems).toContain('Add the part of the finding that matters most right now.'); }); + it('renders a search-readiness answer instead of clarify guidance when viability preflight is unavailable', async () => { + searchClient.evaluateSuggestions.and.returnValue(of(null)); + + component.onFocus(); + fixture.detectChanges(); + component.onQueryChange('database connectivity'); + await waitForDebounce(); + fixture.detectChanges(); + + const answerPanel = fixture.nativeElement.querySelector('[data-answer-status="insufficient"]') as HTMLElement | null; + const guidanceItems = fixture.nativeElement.querySelectorAll('[data-answer-guidance="clarify"]'); + + expect(answerPanel).not.toBeNull(); + expect(answerPanel?.textContent).toContain('Search needs live data'); + expect(answerPanel?.textContent).toContain('returned zero indexed matches'); + expect(answerPanel?.textContent).toContain('suggestion verification is also unavailable'); + expect(guidanceItems.length).toBe(0); + }); + it('does not hard-filter search requests to the current route scope', async () => { ambientContext.buildContextFilter.and.returnValue({ domains: ['findings'] } as any); diff --git a/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts index cd9108e41..4591f5acc 100644 --- a/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/unified-search-experience-quality.e2e.spec.ts @@ -362,6 +362,34 @@ test.describe('Unified Search - Experience Quality UX', () => { await expect(page.locator('app-global-search input[type="text"]')).toHaveValue('mystery release blocker'); }); + test('shows search-readiness guidance instead of blaming the query when the live search corpus is unavailable', async ({ page }) => { + await page.unroute('**/api/v1/search/suggestions/evaluate'); + await page.route('**/api/v1/search/suggestions/evaluate', async (route) => + route.fulfill({ + status: 404, + body: '', + }), + ); + await mockSearchResponses(page, () => emptyResponse('database connectivity')); + + await page.goto('/releases/versions'); + await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 }); + + const searchInput = page.locator('app-global-search input[type="text"]'); + await searchInput.focus(); + await waitForResults(page); + await searchInput.fill('database connectivity'); + await searchInput.press('Enter'); + await waitForResults(page); + + const answerPanel = page.locator('[data-answer-status="insufficient"]'); + await expect(answerPanel).toBeVisible(); + await expect(answerPanel).toContainText(/search needs live data/i); + await expect(answerPanel).toContainText(/returned zero indexed matches/i); + await expect(answerPanel).toContainText(/suggestion verification is also unavailable/i); + await expect(page.locator('[data-answer-guidance="clarify"]')).toHaveCount(0); + }); + test('uses backend answer framing and shows overflow as secondary results without manual filters', async ({ page }) => { await mockSearchResponses(page, (query) => query.includes('critical findings') diff --git a/src/Web/StellaOps.Web/vitest.codex.config.ts b/src/Web/StellaOps.Web/vitest.codex.config.ts new file mode 100644 index 000000000..8ec6bb86a --- /dev/null +++ b/src/Web/StellaOps.Web/vitest.codex.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + pool: 'threads', + poolOptions: { + threads: { + singleThread: true, + }, + }, + fileParallelism: false, + isolate: false, + maxWorkers: 1, + minWorkers: 1, + }, +});