Skip to content

Post-Tailwind-v4 SCSS cleanup: 280 → 128 stylesheets#5357

Merged
norman-abramovitz merged 27 commits into
cloudfoundry:developfrom
nabramovitz:feature/post-tailwind-v4-theming
May 21, 2026
Merged

Post-Tailwind-v4 SCSS cleanup: 280 → 128 stylesheets#5357
norman-abramovitz merged 27 commits into
cloudfoundry:developfrom
nabramovitz:feature/post-tailwind-v4-theming

Conversation

@nabramovitz
Copy link
Copy Markdown
Contributor

@nabramovitz nabramovitz commented May 21, 2026

Summary

Eliminates 152 SCSS files (280 → 128, -54%) in the post-Tailwind-v4 cleanup. Class A files (utility-only rules) are inlined into templates and deleted. Class B files (BEM + :host blocks) are rewritten as @apply directives in retained stylesheets. The largest single-pattern cluster — 21 cf signal-tab and stepper-step components with single-rule :host boilerplate — is collapsed onto two shared utility classes applied via host: { class: '...' } in @Component decorators, deleting the stylesheets entirely.

Net: 403 files changed, 407 insertions, 2764 deletions. Component public APIs unchanged.

Approach

  • Class A (utility-only) — inline Tailwind utilities into the template, drop styleUrls, delete the .scss. Example: app.component.scss :host { display:flex; flex-direction:column; flex:1 1 0%; height:100vh; min-height:0 }host: { class: 'flex flex-col flex-1 h-screen min-h-0' }.
  • Class B (structural HTML can't change) — keep the stylesheet but rewrite every rule as @apply. Selectors stay; only property values change.
  • Shared :host utility classes (batch 12) — add .app-host-fill (@apply block h-full min-h-0) and .app-host-flex-1 (@apply flex-1) to core/src/styles.scss. Apply via host: { class: '...' } so trivial single-rule :host stylesheets can be deleted outright.

Commit groupings (26 commits)

  • 7c6b56c035 — Remove 2 orphan component SCSS files
  • d856ca4a45 — Migrate 35 Class A SCSS to Tailwind utilities
  • e431428360, e11ee121f2 — Remove 9 dead-rule Class B SCSS files
  • 621f8e7175 — Flatten entity-summary-title BEM to template utilities
  • 31ade961c4 — Remove dead-rule table-cell-select-org SCSS
  • 47fa9ca3fd — Migrate 7 Class B in kubernetes
  • bf18b1591d — Migrate 3 Class B in cf-autoscaler
  • 5 cloud-foundry batches (e9298f6173..87417a2f64) — 22 Class B files
  • 11 core batches (e309956bd9..af000c79d6) — 41 Class B files
  • 0eddc659aaBatch 12: 21 cf :host SCSS → shared utility classes
  • cf38bf1cd4Batch 13: 10 Class A core+k8s SCSS → host:{class} + template inlines

Test plan

  • make build green (0 errors, 14.4s, full cross-compile of jetstream backends)
  • Playwright visual sweep at https://localhost:5540 — no regressions on:
    • Login page (app.component host-class)
    • /endpoints page (endpoints-page + signal-list + missing host-classes)
    • /cloud-foundry list page
    • CF endpoint dashboard (/stacks + signal-tab cluster)
    • CF Applications tab (cloud-foundry-applications-signal — batch 12 fill cluster)
    • CF Routes tab (same cluster)
    • CF Organizations tab + Create Organization wizard (batch 12 flex-1 cluster)
  • Component public APIs unchanged — only styleUrls removals and host: { class } additions in @Component decorators

Not in this PR (parked follow-up workstream)

  • 30 Class C files — need CSS restructuring (@keyframesanimate-*, :host-context(.dark)dark: variant, ::ng-deep reworkable via wrapper class)
  • 18 Class D files — require extension/library swaps (Sass-map theme partials driving :root/dark-theme CSS-vars, parameterized @mixin definitions, ::ng-deep into ngx-charts framework internals, global stylesheets loaded outside styleUrls)
  • ~28 easier-B files with BEM + descendant rules — technically .scss-only rewrites but bundled with the C/D workstream

Note: pre-existing unrelated bug surfaced during verification

The CF Organizations signal-list refresh() action updates the total-count signal but does NOT re-fetch the row dataset. After creating an org via the wizard, the list shows stale rows until a full navigation. Bug lives in cf-orgs-signal-config.service.ts (and parallel CfApps/CfRoutes/CfServices config services likely share it). Not caused by this PR — the host-class refactor only touches the layout, not the data flow.

Release sequence

  1. Merge this PR — lands the Class A/B SCSS cleanup (280 → 128 stylesheets).
  2. Fixup-broken-operations PR — addresses UI regressions and pre-existing bugs surfaced during the post-Tailwind-v4 visual sweep, including the orgs-refresh state bug noted above (and any parallel CfApps/CfRoutes/CfServices signal-config bugs found while sweeping). Component-level changes, not SCSS.
  3. Alpha release — cut from develop after the fixup pass lands, giving testers a coherent baseline that combines the Class A/B cleanup with the operational fixes.

Whether the remaining Class C/D SCSS work (and the ~28 easier-B files bundled with it) lands inside the alpha cycle or after is TBD — it depends on the extension-fixup analysis, which has not started yet.

Neither file was loaded by its component:

- card-app-status.component.scss: parent .ts has no styleUrls,
  so .card-app-status__large { font-size: 16px } never applied.
- custom-expansion-panel.component.scss: stale duplicate of the
  inline styles: [...] block already declared on the @component.

Found via SCSS classifier styleUrls cross-reference. No callers
in the tree reference either path.
Each component .scss either declared rules whose selectors never
matched any element in the component's template (dead) or declared
trivial single-class rules that translate directly to Tailwind
utility classes in the template.

Migration per file:
- delete the .scss
- remove styleUrls from the @component decorator
- where the rule was live: replace the SCSS class on the element
  with the equivalent utility string

Net: 35 .scss files deleted, no behavioral change. Production build
passes with 0 errors. Bundle transfer size unchanged.

SCSS count in src/frontend/packages: 281 -> 244.
The classifier's enhanced dead-rule check (cross-references every styleUrls
consumer's template + .ts) caught these: their selectors target nothing in
any consumer's markup. They survived earlier Class A passes because they
had bare-tag-nesting (`mat-icon { ... }`, `div { > span { ... } }`) which
the classifier treated as Class B, but the bare tags themselves were also
absent from the templates after v4 work.

Removed:
- cf-autoscaler/.../edit-autoscaler-policy-step2.component.scss
- cf-autoscaler/.../app-autoscaler-metric-chart-card.component.scss
- cloud-foundry/.../table-cell-confirm-org-space.component.scss
- kubernetes/.../kubernetes-node-ips.component.scss
- kubernetes/.../kubernetes-node-labels.component.scss
- kubernetes/.../kubernetes-node-link.component.scss

Each .ts loses its styleUrls entry. No template changes. Production build
passes with 0 errors.

SCSS count in src/frontend/packages: 244 -> 238.
The .scss declared 12 BEM-style rules (.entity-summary, &__img, &__logo,
&__sub-header, &__content, etc.) — 10 used in the template, 2 dead. The
classifier marked the file Class B because of BEM nesting; flattening
each rule's CSS to inline Tailwind utilities on the matching element
eliminates the .scss entirely.

Migration:
- .entity-summary       -> my-[10px]
- &__header-section     -> flex m-[10px]
- &__img                -> grow-0 shrink-0 basis-16 max-h-16 self-center h-16 object-contain w-16
- &__logo               -> grow-0 shrink-0 basis-16 max-h-16 text-[54px]
- &__header-inner       -> flex-1 ml-[10px]
- &__header-wrapper     -> flex items-baseline
- &__header             -> m-0
- &__header-info        -> text-xl ml-[10px] opacity-40
- &__sub-header         -> text-base -mt-[3px] opacity-60
- &__sub-text           -> text-base opacity-60 pt-[10px]
- &__content            -> mt-0 -mx-[20px] -mb-[20px] pt-0 px-[20px] pb-[20px]
- &__dashboard, &__spacing dropped (dead)

BEM class names dropped from template — no external CSS or ::ng-deep
references found across the codebase. Production build passes with 0
errors. SCSS count: 238 -> 237.
Manual verification across the git and cf-autoscaler packages caught
three additional files whose selectors are absent from their consumers'
templates. The classifier missed these because nested bare-tag rules
(`.parent__icon img { ... }`) were treated as live when any `<img>`
appears in the template, even when the `.parent__icon` wrapper itself
is absent.

Removed:
- git/.../git-endpoint-details.component.scss
- git/.../github-commit-author.component.scss
- cf-autoscaler/.../card-autoscaler-default.component.scss

Each .ts loses its styleUrls entry. No template changes. Production
build passes with 0 errors.

SCSS count in src/frontend/packages: 237 -> 234.
Each file's class selectors flattened into the template, nested
bare-tag rules dropped (dead targets) or inlined onto matching
elements. Production build passes with 0 errors.

Per file:

panel.component
- 5 simple @apply classes (.panel, --background, --container, --border,
  __title) inlined; ngClass map switched from BEM names to utility
  strings so the conditional behavior is preserved.

catalog-tab.component
- 2 BEM classes (charts__repos--collapse, --expand) inlined via
  [ngClass]. `app-list { flex: 4 }` rule dropped — template uses
  <app-signal-list>, not <app-list>.

kubernetes-certs-auth-form.component
- .kube-certs-auth__content inlined as `flex flex-col gap-3 pt-4`.
  Nested textarea rule dropped — template-inlined `min-h-[200px]
  font-mono text-sm` overrode the SCSS-specified 60-75px range
  anyway. `> * { margin-bottom: 10px }` dropped — `gap-3` already
  provides equivalent inter-child spacing.

kubernetes-serviceaccount-auth-form.component
- .kube-sa-auth__form inlined as `flex flex-col`. Nested textarea
  rule (font, line-break) inlined onto the textarea as `font-mono
  [line-break:anywhere]`.

kubernetes-resource-list.component
- .sub-nav-menu-button / __btn inlined as `rounded ml-5 w-[200px]`
  on wrapper and `w-full leading-7 py-0 pr-2 pl-3` on the button.
  __icon class dropped (no rule). `.menu` and `.btn-wrapper` rules
  dropped — neither selector appears in the template.

kubernetes-resource-viewer.component
- .resource-preview__side-by-side inlined as `flex space-x-10`
  (replaces `app-metadata-item { margin-right: 40px }` nested rule).
  .resource-preview__header inlined as `flex justify-end`. Nested
  `mat-icon` rule dropped — template uses `<span class="material-
  icons">`, not <mat-icon>.

kubernetes-pod-containers.component
- .pod-container-icon inlined as `flex items-center h-15 w-[50px]`.
  Nested `mat-icon` rule dropped — template uses <span>, not
  <mat-icon>.

SCSS count in src/frontend/packages: 234 -> 227.
Audit-trail for dropped paths used class-attr / ngClass-literal-key
matching (not bare substring grep). The earlier substring approach
gave false positives against button IDs (e.g. autoscaler-policy-edit
matching id="autoscaler-policy-edit-edit") and Angular component
selectors (e.g. app-metadata matching <app-metadata-item>).

autoscaler-tab-extension.component
- 14 BEM/utility classes flattened to template (.autoscaler-tab,
  __actions, __latest-metrics, .app-metadata, .app-metadata-table,
  __two-cols, .app-autoscaler-tile-grid-100, .autoscaler-tab-table-
  no-record, .autoscaler-tile-events, __no-policy, __header,
  .nav-button-with-text, __span, __icon).
- Nested .app-metadata app-metadata-item / :first-child rules
  combined as `my-0` on the one app-metadata-item per iteration.
- Dropped as dead (verified absent from template):
  * .table-header:first-of-type — no `class="table-header"` in
    template; <th> elements have inline `px-6 py-3 ...` utilities
  * <table> / <td> bare-tag rules — tables have `min-w-full
    divide-y ...`; tds have `px-6 py-4 ...`; the SCSS w-full / pl-2
    were redundant or overridden
  * .autoscaler-tile-events__header .card-title — no .card-title
    class; element is <app-card-title> Angular component (parent's
    emulated-encapsulation SCSS cannot pierce into the child
    component's rendered output)
  * .autoscaler-tile-events__header button — only direct child
    under <app-card-header class="autoscaler-tile-events__header">
    is <app-card-title>, no <button>

edit-autoscaler-policy-step3.component
- ALL 10 SCSS classes (radio-outer-circle, app-autoscaler-tile-grid-
  100, autoscaler-policy-edit, __actions, app-metadata, __two-cols,
  autoscaler-policy-edit-recurring, form-field-left, form-field-30,
  form-field-50) are absent from the template (which uses .card,
  .card-header, .card-title, .form-group, .label, .input, .radio,
  .radio-label, etc.). File is fully dead — delete + drop styleUrls.

edit-autoscaler-policy-step4.component
- ALL 8 SCSS classes are absent from the template (same disposition
  as step3). File is fully dead — delete + drop styleUrls.

Production build passes with 0 errors.
SCSS count in src/frontend/packages: 227 -> 224.
.form-field { @apply pt-1.5 } targets a class that does not appear in
the template. The template uses <app-form-field> Angular component,
which is a selector, not a `.form-field` CSS class — and Angular's
emulated encapsulation prevents the parent's SCSS from reaching into
the child component's rendered output.

Production build passes with 0 errors.
SCSS count in src/frontend/packages: 224 -> 223.
Verified each dropped path with class-attr / ngClass-literal-key
matching against the template. Production build passes with 0
errors. SCSS count: 223 -> 218.

table-cell-edit-variable.component
- .cell-edit-variable -> w-full
- .cell-value-variable -> cursor-pointer
- Dropped .form-field .form-field-infix (no .form-field in template;
  <app-form-field> is an Angular component selector, not a CSS class)

cli-info.component
- .cli-info -> flex flex-col h-full w-full
- .cli-info__content -> flex-1 pb-12
- Dropped .cli-info__footer (not in template)

service-icon.component
- .service-icon -> self-end flex
- .service-icon__padding (conditional via [ngClass]) -> pr-6
- Nested rule for img/mat-icon/app-custom-icon/.custom-icon sizing
  inlined on the matching elements: <app-custom-icon> gets
  `text-[48px] h-12 w-12`; <img> gets `h-12 w-12` (font-size no-op
  on img). Dropped mat-icon and .custom-icon target selectors —
  not in template.

table-cell-service.component
- div > div top-level rule inlined as `pb-[5px]` on each direct-
  child <div> (5 elements) — preserves original padding-bottom
  behavior including for the last child.
- .broker -> flex (2 occurrences)
- Nested app-table-cell-service-broker pl-[5px] inlined on the
  2 <app-table-cell-service-broker> elements inside .broker.

gitscm-tab.component
- .gitscm-tab__deployment__commit-warning -> flex flex-row
- .gitscm-tab__deployment__commit-warning__msg -> pl-6
- .gitscm-tab__repo__name -> flex items-center break-all
- .gitscm-tab__repo__name a -> flex-1 (on the <a> inside)
- .gitscm-tab__repo__name--icon -> text-[18px] h-[18px] leading-
  [18px] ml-[5px] mr-0 w-[18px]
- Dropped class-only-no-rule selectors (.gitscm-tab,
  __deployment, __deployment__commit, __avatar — no styling)
Each class verified live or dead with class-attr / ngClass-literal-key
matching. Production build passes with 0 errors. SCSS count: 218 -> 213.

cloud-foundry-cell-summary.component
- .cell-summary__summary -> mb-5
- .cell-summary__summary__app-metadata -> flex flex-row
- .cell-summary__summary__app-metadata--twoCols -> flex-1
- Nested app-metadata-item:first-child mt-0 -> applied to the first
  <app-metadata-item> in each twoCols div (2 elements)
- Dropped class-only-no-rule .cell-summary, __usage, __health

cf-admin-add-user-warning.component
- .user-warning -> items-center flex text-sm mb-3 px-[22px] py-3
- .user-warning__icon -> text-[22px] mr-[13px]

application-delete.component
- .app-delete__monitors -> flex flex-col w-full (4 occurrences)
- .app-delete__step-table -> w-full (2 occurrences)
- Dropped .app-delete__monitor (no class= match in template)

cf-service-card.component
- .service-card__title -> flex flex-row justify-between w-full
- Dropped .service-card (no own rule, just a parent BEM anchor)
- Dropped .service-card__icon-link and .service-card__title__link
  (no class= match in template)

add-service-instance-base-step.component
- .select-service-step -> w-full
- .select-service-step__help -> pb-[10px]
Class-attr / ngClass-literal-key matching used to verify live/dead.
Production build passes with 0 errors. SCSS count: 213 -> 209.

cloud-foundry-invite-user-link.component
- .invite-user-details -> mb-3 px-6 py-1.5
- .invite-user-details__text -> pr-[5px]
- .invite-user-details__content -> flex items-baseline
- Dropped .invite-user-details__button (no rule defined)

cf-endpoint-details.component
- .cf-details -> flex flex-col
- .cf-details__line -> flex items-center
- .cf-details__icon -> flex items-center justify-center mr-2

env-var-view.component
- .env-var-view -> flex flex-col
- .env-var-view__content__key -> mb-0
- .env-var-view__content__value -> mt-0 whitespace-pre-wrap
- .env-var-view__actions -> flex flex-row-reverse justify-self-start
- Dropped class-only-no-rule .env-var-view__content

service-summary-card.component
- .service-summary__title -> flex flex-row justify-between
- .service-summary__description -> leading-normal (CSS 1.5em line-
  height ≡ unitless 1.5 multiplier for non-inheriting context)
- Dropped class-only-no-rule .service-summary, __title__link
Class-attr / ngClass-literal-key matching used to verify live/dead.
Production build passes with 0 errors. SCSS count: 209 -> 204.

card-cf-info.component
- .app-metadata -> flex flex-row
- .app-metadata__two-cols -> flex-1 (first app-metadata-item gets mt-0)
- .app-metadata__address -> flex min-h-6
- .app-metadata__address__value -> items-center flex mr-7 break-all
- .user-invites button (nested) -> [line-height:initial] m-0
  min-w-[auto] p-0 inlined on each <button> (2 buttons). Wrapper
  .user-invites class dropped (no own rule).

card-cf-org-user-details.component / card-cf-space-details.component
- .app-metadata -> flex flex-row
- .app-metadata__two-cols -> flex-1 (first app-metadata-item gets mt-0)
- .card-title -> font-medium (locally scoped — verified no global
  .card-title rule in core/cloud-foundry/theme; only same-pattern
  component-scoped rules exist elsewhere)

service-instance-last-op.component
- .last-op--row1 -> items-center flex pb-[5px] + conditional
  justify-end/justify-start via [ngClass] (replaces the
  `.align-right .last-op--row1` descendant override)
- .last-op--item -> conditional pl-[5px] pr-0 / pr-[5px] via
  [ngClass] on each <app-boolean-indicator> (2 elements)
- Dropped class-only-no-rule .last-op, .align-right, .last-op--row2

application-action-bar.component
- .summary__sub-nav -> flex w-full
- .summary__actions -> flex
- .summary__anchor -> flex justify-center (on <a>)
- Dropped .summary__sub-nav__poll (no class= match in template)
This empties cloud-foundry Class B. Production build passes with 0
errors. SCSS count: 204 -> 201.

build-tab.component
- .summary__staging-error -> items-center flex (nested mat-icon
  rule dropped — template uses <span class="material-icons">)
- .app-metadata -> flex flex-row
- .app-metadata__two-cols -> flex-1 (first app-metadata-item: mt-0)
- .cli-link -> mt-6
- .summary__gitscm -> flex; nested `div { flex: 1 }` inlined as
  flex-1 on each of the 2 inner <div>s; nested `app-metadata-item:
  first-of-type { mt-0 }` inlined on the first <app-metadata-item>
  in each div
- Dropped dead .summary, __cards, __card, __large, __tile_actions,
  __actions, __sub-nav, __sub-nav__poll, __anchor (none of these
  selectors appear as class= in this template — the build-tab file
  was a copy-paste of application-action-bar styles before the
  sub-nav block was moved out to its own component; comment in the
  original .scss already flags this. Verified no class= matches.)

deploy-application-fs.component
- .deploy-app-local__title -> font-semibold my-6 (2 occurrences)
- .deploy-app-local__input -> h-0 invisible w-0 (2 occurrences)
- .deploy-app-local__info -> mt-6
- .deploy-app-local__indicator -> items-center flex mb-3 (2
  occurrences); nested mat-icon rule dropped — template uses <span
  class="material-icons">, not <mat-icon>
- .deploy-app-local__next -> pt-6
- Nested `button { margin: 0 1px }` -> mx-px inlined on the 2
  <button class="btn btn-primary"> elements
- Dropped .deploy-app-local parent (no own rule)

quota-definition-base.component (cross-consumer)
- This .scss is loaded by two sibling consumer components via
  ../quota-definition-base/... relative styleUrls — neither has its
  own .scss. Both consumer templates flattened identically:
    quota-definition.component.html
    space-quota-definition.component.html
- .quota-definition-base parent class dropped from both (no own
  rule); the page-scope classes (.quota-definition-page,
  .space-quota-definition-page) kept on the wrapper div in case
  consumers add their own styling later.
- Replacements (all replace_all on both templates):
    __name-sub-text    -> text-sm opacity-60
    __name             -> m-0
    __basic-services   -> flex my-2
    __basic-services-label -> mr-[10px] opacity-60
    __title            -> my-[10px]
    __section-header   -> my-[10px] opacity-60
- styleUrls entry removed from both consumer .ts files; the .scss
  is deleted.
Three pure dead-rule removals plus one near-dead migration.
SCSS files deleted and their styleUrls entries dropped.

- features/setup/domain-mismatch: template already uses
  Tailwind utilities; all .domain* classes dead.
- features/setup/upgrade-page: template already Tailwind-only;
  all .upgrade* classes dead.
- shared/components/boolean-indicator: SCSS uses
  .boolean-indicator__* BEM but template uses single-dash
  .boolean-indicator-container; nested mat-icon rules dead
  (template uses <span class="material-icons">).
- shared/components/cards/card-boolean-metric: same BEM/dash
  mismatch; one live root rule
  (.boolean-metric-card { height: 100% }) inlined as h-full
  on the root div.

Build green.
All five files were thin BEM wrappers with a single live
class each — straightforward Tailwind inlining.

- connect-endpoint: drop parent .connect (no rules); replace
  .connect__auth-type with w-full on app-form-field.
- connect-endpoint-dialog/auth-forms/credentials-auth-form:
  .credentials-auth__form -> flex flex-col.
- connect-endpoint-dialog/auth-forms/none-auth-form:
  .none-auth__form -> flex flex-col mb-6 (24px).
- connect-endpoint-dialog/auth-forms/sso-auth-form:
  .sso-auth__form -> flex flex-col.
- create-endpoint/create-endpoint-base-step: .select-step
  -> w-full; .select-step__help was dead (only referenced in
  a commented-out <p> block).

Build green.
- features/api-keys/api-keys-page: pure dead-rule removal.
  All nested rules targeted mat-card / mat-card-header /
  mat-icon, but the template uses app-card / app-card-header
  / app-custom-icon.
- features/metrics/metrics-endpoint-details: .metrics-details
  -> flex flex-col; __line -> flex items-center; __icon ->
  flex items-center justify-center mr-2 (preserving text-
  warning).
- shared/components/list/list-types/endpoint/table-cell-
  endpoint-address: .endpoint-address-cell -> flex items-
  center; __copy -> w-6.
- shared/components/list/list-types/endpoint/table-cell-
  endpoint-name: .endpoint-name-cell -> flex items-center;
  __icon -> text-[20px] h-5 w-5 pl-2.
- shared/components/list/max-list-message: .maxed -> flex
  flex-col items-center; __load was dead.

Build green.
- features/login/logout-page: .logout (no rules) dropped;
  .logout__card -> p-0 w-[300px]; .logout__body -> p-6
  text-center; .logout__msg -> text-lg pb-5; .logout__loading
  had no rule.
- features/home/home/default-endpoint-home-component:
  .default-home-card -> m-4; __address -> flex min-h-6;
  __address__value -> flex items-center mr-7 break-all.
- features/home/home/favorites-side-panel: __card on
  <app-favorites-meta-card> -> flex flex-col mb-1.5; parent
  .fav-side-panel rule had no own body.
- features/user-profile/edit-profile-info: .edit-profile
  (mt-2.5) + .edit-profile__group (flex flex-col max-w-full)
  combined into single class attribute on form root; second
  occurrence on inner div migrated. Nested .custom-form-field
  rule was dead (component selector under emulated scope).
- core/entity-favorite-star: .favorite-star -> flex
  items-center cursor-pointer; conditional size via
  [ngClass]="small ? 'h-5 w-5' : 'h-6 w-6'". Nested mat-icon
  rule was dead (template uses <app-custom-icon>).

Build green.
- shared/components/display-value: .display-value ->
  min-w-[200px]; __label -> font-bold text-sm mb-2.5.
- shared/components/page-sub-nav-section: .sub-nav-section ->
  inline-block; __inner -> flex items-center; __divider ->
  border-r border-[rgba(100,121,143,0.4)] h-5 mr-2.5 pr-2.5.
- shared/components/stacked-input-actions: only live rule
  was __add--content -> flex items-center; parent shells
  and nested mat-icon were dead (template uses
  <app-custom-icon>).
- shared/components/start-end-date: .start-end-date -> flex
  items-center flex-wrap w-full; __selector -> flex-none
  px-[5px]; __arrow -> px-[5px]; .invalid-message -> text-xs
  min-h-4.
- shared/components/list/list-table/table-cell-expander:
  drop .expander (template already had cursor-pointer
  inline-flex items-center justify-center w-6 h-6);
  __icon-container -> h-[18px] w-[8px].

Build green.
- shared/components/user-profile-banner: .user-profile-banner
  -> flex flex-row p-6; __avatar -> flex-none mr-6; __email
  -> text-base; __title -> flex-1 text-[28px]. Nested mat-icon
  rule was dead (template uses <app-user-avatar>).
- shared/components/application-state: .app-state -> flex
  flex-row items-center; __with-icon -> conditional h-[25px]
  via [class.h-\[25px\]]; __label and __sub-label ->
  overflow-hidden text-ellipsis; nested
  app-application-state-icon padding-right inlined as pr-[5px]
  on the component element.
- shared/components/list/list-table/app-table-cell-default:
  .link parent had no rules; __short -> flex; __short__icon
  -> text-[17px]. Nested mat-icon rule was dead (template
  uses <app-custom-icon>).

Build green.
- shared/components/chips: drop .app-chips on container
  (replaced with my-[5px]); drop .app-chip__container (was
  redundant flex items-center); __label -> flex-1 h-[18px]
  leading-[18px] break-all; __close -> text-[15px] h-[15px]
  w-[15px] flex-none ml-[5px]; __busy-spinner -> flex-none
  ml-[5px]; __limit on button -> cursor-pointer font-light
  min-w-0 px-[5px]; removed no-rule __show-more and
  __show-less conditional ngClass.
- shared/components/code-block: .app-code-block ->
  p-[10px_20px] bg-[var(--code-block-bg)] border
  border-[var(--code-block-border)]
  text-[var(--code-block-text)]; __content -> flex flex-col
  justify-center mr-[20px] (added to existing utilities);
  __pre conditional -> [class.px-[20px]]; __copy ->
  top-[10px] right-[20px] (replacing prior top-2 right-2 to
  match SCSS).
- shared/components/list/list-table/table-cell-edit: .edit
  -> flex gap-1 justify-end; --subtle modifier was a
  parent-conditional rule against .btn-icon descendants;
  rewrote as per-button [ngClass]="subtle ? '...' : ''" with
  the exact utility set inlined on each of the three edit/
  save/cancel buttons.

Build green.
- shared/components/user-avatar: parent-conditional
  __large state rewritten as size-aware [ngClass] on each
  child. Base: flex items-center justify-center. Root gets
  h-16 w-16 when large. Placeholder span: large -> text-[64px]
  h-16 w-16; small -> text-[36px] h-9 w-9 leading-[initial].
  Initials div: large -> text-[26px] h-[60px] w-[60px]
  leading-[60px]; small -> text-sm h-[30px] w-[30px]
  leading-[30px]; rounded-full text-center always. Gravatar
  img: large -> h-16 w-16; small -> h-9 w-9; rounded-full
  always.
- shared/components/file-input: .file-input -> flex flex-col;
  __disabled rewritten as [class.opacity-50]="disabled"
  [class.pointer-events-none]="disabled" on root. .file-input
  __input -> flex-1 bg-transparent border-none outline-none
  text-[inherit] font-[inherit] p-0 min-w-0. __button -> btn
  btn-sm btn-secondary ml-1 flex-none leading-[inherit]
  min-w-[auto] px-1. __field __form-field were redundant
  containers; __none and __hint had no template references
  (dead).

Build green.
- shared/components/stratos-title: .stratos-title -> flex
  flex-col justify-center pt-6 (text-center already on
  template); __logo -> self-center pt-3 pb-4 w-60 (preserving
  existing mx-auto mb-4); __header had display:none -> hidden
  (h1 stays in DOM for accessibility but visually hidden as
  the logo image is the visual title); __subtitle p element
  had no SCSS rule, just kept its existing utilities.
- shared/components/list/list-types/endpoint/table-cell-
  endpoint-status: bare div selector inlined as flex
  items-center on the root div; bare span { margin-right:
  8px } rule was global to all descendant spans (including
  nested (local) spans); migrated as explicit mr-2 on each
  text-label span and on the two nested (local) spans to
  preserve original cascade behavior.
- shared/components/copy-to-clipboard: parent-conditional
  .copy__success rule replaced by direct opacity bindings on
  each child. .copy__copied-div -> absolute right-0 top-0
  flex items-center gap-1 transition-opacity duration-300
  with [class.opacity-100]/[class.opacity-0] driven by
  copySuccessWait. .copy__copy-icon -> absolute right-0 top-0
  transition-opacity duration-300 with inverse opacity
  binding. The bare-span margin-right: 5px under
  __copied-div migrated as mr-[5px] on the msg and icon
  spans. .copy__copy-icon-small was dead (no template ref).

Build green.
Both files used parent-conditional state CSS that toggled
child styles when a state class was present on the root.
Rewritten as direct conditional [ngClass] / [class.X] on
each affected element keyed off the same signal.

- shared/components/sidepanel-preview: root no longer carries
  .sidepanel-preview / .sidepanel-preview__preview; instead
  each affected element binds against
  sidePanelService.previewMode():
    - root: h-[calc(100vh-57px)] vs h-screen
    - header: h-[48px] flex-[0_0_48px] vs h-14 flex-[0_0_56px]
    - h1: text-[16px] vs text-xl
    - close button top: top-[3px] vs top-[7px]
  Static utilities (bg/border/CSS vars, header layout,
  favorite, custom slot, content) inlined directly.
- shared/components/upload-progress-indicator: dropped the
  parent .upload-progress-done toggle; each of the three
  children (cloud icon, done icon, spinner) binds
  [class.opacity-0]/[class.opacity-100] off value === 100,
  using the same transition-opacity duration-1000 base.
  Sass $progress-size (140px) and $progress-transition-speed
  (1s) inlined as h-[140px] / w-[140px] / duration-1000.

Build green.
Re-audited the four files the read-only audit agents had
flagged as Class C. All four turned out to be migratable
after closer inspection.

- features/user-profile/profile-info: ENTIRELY dead. None of
  .user-profile / .user-profile__* / mat-card:not(:first-
  child) match the template (template uses .card / .card-
  header / .card-body Tailwind component classes and no
  <mat-card>). Just delete + drop styleUrls.
- core/log-out-dialog: mat-dialog-actions bare selector and
  __message > mat-icon nested rule were dead (template uses
  <div class="dialog-actions"> and <i class="material-icons">
  respectively). Live rules inlined: __outer ->
  m-[-24px_-24px_0] p-[24px_50px] relative; __progress ->
  absolute inset-x-0 top-0; __content -> p-5; __message ->
  flex flex-row items-center; __message > div bare-tag ->
  flex-1 text-lg font-bold; __actions -> justify-center.
- features/setup/local-account-wizard: all .local-setup-
  wizard* BEM was dead (template never uses them). All
  nested app-steppers .child { @apply ... } rules were dead
  under emulated encapsulation (the child elements live in
  the steppers child component's own template). app-stratos-
  title rule dead (no <app-stratos-title> in template).
  Three live rules inlined: .page-container -> flex flex-
  col h-full on root; bare form -> min-w-[400px] w-full on
  the password form; app-steppers host -> class="flex flex-1
  my-6".
- features/setup/uaa-wizard/console-uaa-wizard: identical
  shape to local-account-wizard, EXCEPT the component had
  encapsulation: ViewEncapsulation.None, which made the
  otherwise-dead nested rules leak globally to every
  <app-steppers> instance in the app. Switched encapsulation
  back to default (Emulated) and dropped the SCSS. Same
  three live rules inlined as in the sibling wizard, plus
  the second form (UAA Scope step) gets the same
  min-w-[400px] w-full.

41/41 Class B core files now migrated. Build green.
Add .app-host-fill and .app-host-flex-1 to core/src/styles.scss,
then convert 21 cloud-foundry components with single-rule :host
stylesheets to apply the new class via host: { class: '...' } in
the @component decorator.

Removed stylesheets:
- 12 :host { display:block; height:100%; min-height:0 } files
  (cf signal-tab cluster: applications, marketplace, organizations,
  organization-spaces, routes, services, users, plus all 5 nested
  space-* signal tabs)
- 9 :host { flex: 1 } files (create-application step1/step2/step3,
  deploy-application-step2-1, create-organization-step,
  create-space-step, edit-organization-step, detach-apps,
  select-service)

styleUrls entries dropped; no template changes; component public
API unchanged. Build green: 0 errors, 14.4s.
Move single-rule :host stylesheets to host: { class: '...' } in the
@component decorator and inline descendant utilities into templates;
delete the .scss files; drop styleUrls entries.

Files migrated:
- core: app.component, endpoints-page, endpoints-signal-list,
  endpoints-missing, list-cards/card, list-generics/list-view,
  monaco-editor
- kubernetes: kube-config-import, kube-config-selection,
  kubernetes-node-info-card

Template inlines:
- app.component.html: .user-id → .hidden (drops dead .user-id
  rule under emulated encapsulation)
- endpoints-missing: <app-no-content-message> gets flex w-full
  relative overflow-visible classes (replaces descendant rule)
- monaco-editor: #editorContainer gets w-full h-full min-h-[200px]
  utilities directly (drops .monaco-editor-container class)

app.component's html/body/material-app rule block dropped — under
emulated encapsulation it never applied; global html/body styles
already live in core/src/styles.scss.

endpoints-page's redundant app-endpoints-signal-list descendant
rule is dropped via .scss deletion; child component's host class
now carries the same flex utilities.

Build green: 0 errors, 14.4s.
Batch 4 (0093467) renamed the clickable div's class from
.favorite-star to utility classes (flex items-center cursor-pointer).
The spec still queried for .favorite-star, returning null, which
caused all 3 click tests to throw TypeError: Cannot read properties
of null (reading 'click').

Switch the spec's three element.querySelector call sites from
'.favorite-star' to '[role="button"]'. The accessibility attribute
is already on the same div and is semantically stable.
Copy link
Copy Markdown
Contributor

@norman-abramovitz norman-abramovitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This work on removing SCCS is about 50% complete. Removing most of the remaining SCCS files will require updates to the stratos extensions. The extension updates were not in the picture at this time.

The work to make more of the code Angular 21-native will need to land first, and then a fix-up path will be taken.

@norman-abramovitz norman-abramovitz merged commit a086dab into cloudfoundry:develop May 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants