Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 2 additions & 20 deletions .github/workflows/stb_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ name: STB Tests

# Gate for the Stratos Theme Builder (tools/stb). It is a standalone npm
# project outside the Angular workspace, so the frontend/backend workflows
# never exercise it. The real gate runs only when stb itself changes, or when
# one of the Stratos templates stb is instrumented against changes — a template
# edit can break the snapshot-id <-> routing correspondence the harvest lint
# checks.
# never exercise it. The real gate runs only when stb itself changes.
#
# STB Tests is a REQUIRED status check on develop/main. A required check that is
# `paths`-filtered at the trigger level never reports on PRs that don't touch
Expand Down Expand Up @@ -36,8 +33,7 @@ env:
NODE_VERSION: '24'

jobs:
# Does this PR touch stb or an instrumented template? The gate job gates on
# this output. Add new instrumented templates here as more scenes convert.
# Does this PR touch stb? The gate job gates on this output.
changes:
name: Detect stb changes
runs-on: ubuntu-latest
Expand All @@ -53,10 +49,6 @@ jobs:
filters: |
stb:
- 'tools/stb/**'
- 'src/frontend/packages/core/src/features/login/login-page/login-page.component.html'
- 'src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.html'
- 'src/frontend/packages/core/src/shared/components/dialog-confirm/dialog-confirm.component.html'
- 'src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html'

test:
name: STB Tests
Expand Down Expand Up @@ -93,13 +85,3 @@ jobs:

- name: Unit + integration tests
run: npm test

# Drift check: login live template ids vs the snapshot routing map.
- name: Harvest lint (login template <-> routing drift)
run: npm run lint:harvest

# Drift check: the shared scene's live template ids (stepper +
# dialog-confirm) must exist in its captured snapshot. (login is covered
# by lint:harvest above; app-list is a mock with no static correspondence.)
- name: Template lint (shared live templates <-> snapshot drift)
run: npm run lint:templates
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<app-page-header [endpointIds$]="cfIds$">
<h1 stb-snapshot-id="cf.applications.heading" stba-role="heading" stba-description="page title text for the applications page">Applications</h1>
<h1>Applications</h1>
</app-page-header>
<app-cf-endpoints-missing #appEndpointsMissing></app-cf-endpoints-missing>

<!-- Show loading indicator -->
@if ((cloudFoundryService.hasRegisteredCFEndpoints$ | async) === false || (cloudFoundryService.hasConnectedCFEndpoints$ | async) === false) {
<div
class="flex items-center justify-center p-8"
stb-snapshot-id="cf.applications.loading"
role="status">
<div class="flex items-center space-x-3 text-content-muted">
<div class="spinner spinner-primary"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
id="app-login-page"
class="login-page min-h-screen flex items-center justify-center relative"
[ngClass]="{'opacity-50 pointer-events-none': busy$ | async }"
stb-snapshot-id="auth.login.page" stba-role="region" stba-roledescription="page" stba-description="background color for the login page"
>
<!-- Full-screen background -->
<div
Expand All @@ -14,15 +13,13 @@
'background-position': 'center',
'background-repeat': 'no-repeat'
}"
stb-snapshot-id="auth.login.page.background" stba-role="img" stba-description="background image for the login page"
></div>
<!-- Login Form Card -->
<div
class="login-card card w-full max-w-md mx-4 p-8 rounded-lg shadow-lg relative z-10"
[ngStyle]="{ 'background-color': loginCardBackground() }"
[style.--input-bg]="inputBackground()"
[style.--input-border]="inputBorder()"
stb-snapshot-id="auth.login.page.card" stba-role="region" stba-description="card background for the login page"
>
<!-- Logo and Display Name Header -->
<div class="login-header flex items-center space-x-4 mb-6">
Expand All @@ -31,17 +28,16 @@
class="login-logo h-12 w-12 shrink-0"
[src]="themeLogo()"
[alt]="themeDisplayName() || 'Logo'"
stb-snapshot-id="auth.login.page.card.logo" stba-role="img" stba-description="logo image for the login page"
/>
}
<div class="login-title-section flex-1">
@if (showTitle()) {
<h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id="auth.login.page.card.title" stba-role="heading" stba-description="welcome / company greeting heading for the login page">
<h1 class="login-title text-xl font-bold text-content-text mb-1">
{{ themeDisplayName() || themeTitle() || 'Stratos' }}
</h1>
}
@if (themeSubtitle()) {
<p class="login-subtitle text-sm text-content-muted" stb-snapshot-id="auth.login.page.card.subtitle" stba-role="paragraph" stba-description="subtitle for the login page">
<p class="login-subtitle text-sm text-content-muted">
{{ themeSubtitle() }}
</p>
}
Expand All @@ -52,7 +48,7 @@ <h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id
@if (customMessage()) {
<p class="login-message text-sm text-content-muted mb-4"
role="note"
stb-snapshot-id="auth.login.page.card.message" stba-role="note" stba-description="custom message for the login page">
>
{{ customMessage() }}
</p>
}
Expand All @@ -70,7 +66,6 @@ <h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id
placeholder="Username"
type="text"
autocomplete="username"
stb-snapshot-id="auth.login.page.card.username" stba-role="textbox" stba-description="input border color for the login fields"
>
</div>
}
Expand All @@ -87,7 +82,6 @@ <h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id
name="password"
placeholder="Password"
autocomplete="current-password"
stb-snapshot-id="auth.login.page.card.password" stba-role="textbox" stba-description="input border color for the login fields"
>
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<app-show-hide-button (changed)="showPassword = $event"></app-show-hide-button>
Expand All @@ -100,7 +94,6 @@ <h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id
class="btn btn-primary w-full py-3"
type="submit"
[disabled]="(ssoLogin$ | async) === false && !loginForm.valid"
stb-snapshot-id="auth.login.page.card.sign-in" stba-role="button" stba-description="sign-in button color for the login page"
>
Sign In
</button>
Expand All @@ -127,7 +120,6 @@ <h1 class="login-title text-xl font-bold text-content-text mb-1" stb-snapshot-id
'-translate-y-full opacity-0': !displayMessage
}"
role="alert"
stb-snapshot-id="auth.login.page.error" stba-role="alert" stba-description="error/failure message color for the login page"
>
<span class="material-icons text-danger shrink-0">error_outline</span>
<span>{{ displayMessage }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('LoginPageComponent — error banner branding', () => {
fixture.detectChanges();
flushEffects();
fixture.detectChanges();
const banner = fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.error"]');
const banner = fixture.nativeElement.querySelector('#login-error-message');
expect(banner).not.toBeNull();
expect(banner.classList).toContain('alert-danger');
expect(banner.className).not.toContain('bg-danger-50');
Expand Down Expand Up @@ -149,7 +149,7 @@ describe('LoginPageComponent — login-scoped input branding', () => {

it('applies login input branding scoped to the login card', () => {
fixture.detectChanges();
const card = fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.card"]');
const card = fixture.nativeElement.querySelector('.login-card');
expect(card.style.getPropertyValue('--input-bg')).toBe('#222222');
expect(card.style.getPropertyValue('--input-border')).toBe('#ff0000');
});
Expand Down Expand Up @@ -193,55 +193,12 @@ describe('LoginPageComponent — logo and title visibility', () => {

it('hides the login logo when showLogo is false', () => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.card.logo"]')).toBeNull();
expect(fixture.nativeElement.querySelector('.login-logo')).toBeNull();
});

it('hides the login title when showTitle is false', () => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.card.title"]')).toBeNull();
});
});

describe('LoginPageComponent — snapshot-id instrumentation', () => {
let fixture: ComponentFixture<LoginPageComponent>;

beforeEach(async () => {
const themeState = signal<StratosTheme>({
...defaultTheme,
branding: {
...defaultTheme.branding,
loginSubtitle: 'Multi-cloud management',
},
});

const brandingStub = {
theme: themeState.asReadonly(),
} as unknown as StratosBrandingService;

await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NoopAnimationsModule,
createBasicStoreModule(),
LoginPageComponent,
],
providers: [
...STORE_TEST_PROVIDERS,
provideZonelessChangeDetection(),
provideHttpClient(),
provideHttpClientTesting(),
{ provide: StratosBrandingService, useValue: brandingStub },
],
}).compileComponents();

fixture = TestBed.createComponent(LoginPageComponent);
});

it('instruments the login background and subtitle for harvest', () => {
fixture.detectChanges();
const el = fixture.nativeElement;
expect(el.querySelector('[stb-snapshot-id="auth.login.page.background"]')).not.toBeNull();
expect(el.querySelector('[stb-snapshot-id="auth.login.page.card.subtitle"]')).not.toBeNull();
expect(fixture.nativeElement.querySelector('.login-title')).toBeNull();
});
});

Expand Down Expand Up @@ -282,7 +239,7 @@ describe('LoginPageComponent — before-login notice', () => {

it('renders the before-login notice when a custom message is set', () => {
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.card.message"]');
const el = fixture.nativeElement.querySelector('.login-message');
expect(el).not.toBeNull();
expect(el.textContent).toContain('Authorized users only');
});
Expand Down Expand Up @@ -314,6 +271,6 @@ describe('LoginPageComponent — before-login notice hidden when empty', () => {

it('hides the before-login notice when customMessage is empty', () => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('[stb-snapshot-id="auth.login.page.card.message"]')).toBeNull();
expect(fixture.nativeElement.querySelector('.login-message')).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="p-6" stb-snapshot-id="shared.confirm-dialog" stba-role="dialog" stba-roledescription="dialog" stba-description="surface/background colour for the shared confirmation dialog">
<div class="p-6">
<h1 class="confirm-dialog__header text-lg font-semibold mb-4 flex items-center justify-between text-content-text">
<span class="confirm-dialog__header-title" stb-snapshot-id="shared.confirm-dialog.title" stba-role="heading" stba-description="title text for the shared confirmation dialog">{{data.title}}</span>
<span class="confirm-dialog__header-title">{{data.title}}</span>
@if (data.critical || textToMatch) {
<span class="material-icons confirm-dialog__warn-icon text-warning-shade-500">warning</span>
}
Expand All @@ -20,7 +20,7 @@ <h1 class="confirm-dialog__header text-lg font-semibold mb-4 flex items-center j
<div class="confirm-dialog__actions flex gap-2 justify-end mt-6">
<button class="btn btn-secondary" (click)="onNoClick()" cdkFocusInitial>Cancel</button>
@if (data.confirm) {
<button class="confirm-dialog__confirm" stb-snapshot-id="shared.confirm-dialog.confirm" stba-role="button" stba-description="confirm button colour for the shared confirmation dialog"
<button class="confirm-dialog__confirm"
[class]="data.critical || textToMatch ? 'btn btn-danger' : 'btn btn-primary'"
(click)="dialogRef.close(true)"
[disabled]="textToMatch && textToMatch !== matchValue">{{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="steppers-wrapper">
@if (steps && steps.length) {
<div class="steppers" stb-snapshot-id="shared.stepper" stba-role="group" stba-roledescription="stepper" stba-description="surface/background colour for the shared stepper wizard">
<div class="steppers">
<div class="steppers-inner">
<!-- Context summary — surfaces in-progress selections (e.g.
"Creating service instance in {CF} / {Org} / {Space}") so
Expand Down Expand Up @@ -82,7 +82,7 @@
<div class="steppers-navigation flex items-center justify-between border-t pt-6">
<div class="steppers-navigation-left">
@if (steps.length !== 1 || basePreviousRedirect) {
<button id="stepper_previous" stb-snapshot-id="shared.stepper.previous" stba-role="button" stba-description="previous button colour for the shared stepper"
<button id="stepper_previous"
class="btn btn-secondary"
(click)="setActive(currentIndex() - 1)"
[disabled]="!canGoto(currentIndex() - 1) || steps[currentIndex()].disablePrevious">
Expand All @@ -104,7 +104,7 @@
}
<!-- Next/Submit button -->
@if (!steps[currentIndex()].hideNextButton) {
<button id="stepper_next" stb-snapshot-id="shared.stepper.next" stba-role="button" stba-description="next/submit button colour for the shared stepper"
<button id="stepper_next"
class="steppers-navigation-right-next btn flex items-center space-x-2"
[ngClass]="{
'btn-danger': steps[currentIndex()].destructiveStep,
Expand Down
22 changes: 11 additions & 11 deletions tools/stb/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,16 @@ provisional and validated by use, not a closed design.
Stratos assets/branding/ ── boot ──► branded UI
```

1. **Instrument** — Stratos templates carry `stb-snapshot-id` (a stable
dot-path identity, e.g. `auth.login.sign-in`) and the ARIA-mimicking
`stba-role` / `stba-roledescription` / `stba-description` attributes
(see *Source of truth and direction* above). These are plain static
attributes; they have no runtime effect in Stratos today.
1. **Instrument** — at capture time, Stratos templates carried
`stb-snapshot-id` (a stable dot-path identity, e.g. `auth.login.sign-in`)
and the ARIA-mimicking `stba-role` / `stba-roledescription` /
`stba-description` attributes (see *Source of truth and direction* above).
The live templates have since been de-instrumented; the attributes survive
only inside the captured snapshots below.
2. **Harvest / capture** — tooling reads the instrumented DOM into a
**snapshot pack** under `public/snapshots/v1/<scene>/`.
`scripts/harvest-login.ts` extracts the ids/attributes and lint-checks them
against routing; `scripts/generate-model.ts` combines that harvest with the
`scripts/harvest-login.ts` extracts the ids/attributes;
`scripts/generate-model.ts` combines that harvest with the
`values.json` sidecar to (re)build `branding-model.json`.
3. **Load** — at startup the app loads the snapshot pack into signals (see
*State* below).
Expand Down Expand Up @@ -265,10 +266,9 @@ signal updates → effect re-renders / posts `STB_*` messages to the iframe shim
optional `company-config.json` + assets) and `zip.ts`.
- `taxonomy/taxonomy.ts` — term/role taxonomy helpers.
- `scripts/` — `harvest-login.ts` (DOM reader: extracts `stb-snapshot-id` +
`stba-*` per element, + login routing drift lint), `lint-templates.ts`
(asserts a scene's live-template ids exist in its snapshot — currently the
shared scene), `generate-model.ts` (`buildModel(scene, html, values)` →
rebuilds `branding-model.json`), `seed-worklist.ts`.
`stba-*` per element, + snapshot/routing drift lint), `generate-model.ts`
(`buildModel(scene, html, values)` → rebuilds `branding-model.json`),
`seed-worklist.ts`.

## Export bundle

Expand Down
4 changes: 1 addition & 3 deletions tools/stb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
"test:watch": "vitest",
"lint": "eslint src tests",
"format": "prettier --write src tests",
"typecheck": "tsc --noEmit",
"lint:harvest": "node --experimental-strip-types scripts/harvest-login.ts ../../src/frontend/packages/core/src/features/login/login-page/login-page.component.html public/snapshots/v1/login/routing.json",
"lint:templates": "node --experimental-strip-types scripts/lint-templates.ts"
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@codemirror/commands": "^6.6.0",
Expand Down
38 changes: 0 additions & 38 deletions tools/stb/scripts/lint-templates.ts

This file was deleted.

Loading