Verify the Angular 21 upgrade

Sign off the TopFans browser, live SSR, the Firebase SDK rewrite, ngx-bootstrap 21, and one white-label tenant. The build, prerender, SSR, and tenant pipelines already pass on the machine; what remains is behaviour only a browser can show.

commit 265d424b74 Angular 21.2.23 ngx-bootstrap 21.2.2 firebase 11.10.0 CI Node 20 / npm 10.8.1

Already verified on the branch

These passed against commit 265d424b74. Re-run them only if the branch changes.

  • build-qa and build-prod compile. Initial bundle 12.44 MB raw, 2.00 MB transfer. Angular 20 was 12.34 MB and 1.99 MB. The 10 MB budget warning is pre-existing.
  • prerender-all-qa completes. 1025 routes prerendered, sitemap and robots.txt written, exit 0.
  • Express SSR renders real markup. /, /creators?key=x, /topfan-vs-patreon, /pricing, /creators/login return 200 with correct titles. /topfan/?key=x is 301, garbage paths are 404. No runtime error in server output.
  • Serverless target compiles. ng run TopFans:serverless:qa exits 0. Nothing in buildspec-*.yml or deployment_script/ uses this target.
  • White-label generation and one tenant build. generate-white-label-qa produced 25 projects; perfectcrew built and served through SSR with its own title.
  • Clean npm ci applies the patch. postinstall runs patch-package and ngx-intl-tel-input gets the ngx-bootstrap 21 fix.

Use the QA artifact

Verify against the optimized QA build served by the Express SSR process, the same path pm2 runs in production.

npm ci
node --max_old_space_size=10048 ./node_modules/@angular/cli/bin/ng build --configuration=qa --prerender=false
PORT=4000 node dist/TopFans/server/server.mjs

Local Node 24.5 prints an npm 12 warning and cannot run ng update; neither matters for this checklist. CI builds on Node 20 with npm 10.8.1 and .npmrc already sets legacy-peer-deps=true.

Release fingerprint

  • AngularCore 21.2.23, CLI and SSR 21.2.24, Material and CDK 21.2.14
  • TypeScript5.9.3, zone.js 0.15.1
  • Firebasefirebase 11.10.0 Web SDK, @angular/fire removed
  • ngx-bootstrap21.2.2, no more Module.forRoot()
  • PrimeNG21.1.10, ng-select 21.8.2, ng-bootstrap 20.0.0, ngrx 21.1.1
  • FontAwesomeangular-fontawesome 4.0.0, svg-core and icon packs 7.3.1
  • Patchedngx-intl-tel-input 15.0.0 via patches/
  • Architecture heldNgModules, zone.js via provideZoneChangeDetection() in AppModule, *ngIf templates

Firebase without AngularFire

FirebaseService now calls the Web SDK directly. Initialization is lazy, browser-only, and gated on each product's isSupported(). Remote Config updates run inside NgZone.run(). A returning user with a stored token previously never attached onMessage; that is fixed and must be checked, not assumed.

  • Grant notification permission on a fresh profile. A token is issued, the register-token API call succeeds, and the console shows no Firebase initialization error.
  • Send a test push while the tab is in the foreground. Exactly one toast or in-app notification appears. Two means the listener attached twice.
  • Reload as the same user. The stored token is reused, no second registration call fires, and a foreground push still surfaces once.
  • Background push. With the tab hidden, the service worker (firebase-messaging-sw.js) shows the system notification and clicking it opens the app.
  • Remote Config drives the UI. On a page that reads iosPayButtonsConfig, changing the remote value and reloading changes what renders. No manual click should be needed for the view to update.
  • Unsupported browser path. In Safari private mode or with notifications blocked, the app loads and no unhandled rejection appears.
  • SSR stays clean. Server output has no IndexedDB, window is not defined, or Firebase provider errors while requesting pages.

ngx-bootstrap 21 and the phone input

Two things changed under the application. Module.forRoot() is gone, so every module is imported bare. BsDropdownDirective.isOpen is a getter now, so code that assigned it uses toggle() or hide(). ngx-intl-tel-input still calls forRoot() and is patched.

Dropdowns toggled from code

  • Home discover filter. Choosing a filter closes the dropdown and reloads the feed.
  • Forum topic filter. Same behaviour on the forum page.
  • Collaborators filter. Selecting a filter closes the menu and the list resets to page one.
  • Post detail actions menu. After saving an edit the menu closes.
  • Post-submit type menu. Choosing a post type closes the menu.
  • Info icon popovers. Opening one closes the previously open one.

Phone number input

  • Signup and personal info. The country dropdown opens, searches, and selects; the dial code updates.
  • Pro contact form. Same on the marketing pro contact form layout.
  • Lazy vendor CSS. Flags and the dropdown are styled after the first render; there is no unstyled flash on a cold load.

Modals, datepickers, tabs, accordions

  • Alert modal variants. Confirm, cancel, cross button, and dynamic class all behave; page-hidden is added on open and removed on close.
  • Datepicker and daterange picker. Open, choose, and clear on the dynamic-form datepicker and any scheduling flow.
  • Tabs and accordion. Insights tabs and pricing accordions switch content.
  • Typeahead. The one typeahead usage still filters and selects.

Host listeners that lost their $event argument

Angular 21 type-checks host bindings. 49 @HostListener('window:scroll' | 'window:resize', ['$event']) decorators sat on handlers that took no parameters and were changed to pass nothing. Behaviour should be identical; confirm the listeners still fire.

  • Infinite scroll loads more. Home feed, creators list, collaborators, block users, basic see-more, merchandise store, VIP offers.
  • Resize handlers. Home, merchandise store, portfolio, sponsored ad, skyscraper preview modal, VIP offer list, advertisement details switch layout when the window crosses the mobile breakpoint.
  • Table sticky behaviour. The shared table component reacts to window scroll.
  • Click-outside handlers. Any component that closes a menu on document click still does so.

Library and product smoke matrix

Area Pass when
Public pages Home, creator login, creator profile, a comparison page, and a marketing page paint without an empty layout or console exception, and stay populated after hydration.
FontAwesome 7 Brand and solid icons render across header, footer, social links, and buttons. A missing icon renders as a blank box and is easy to miss on dark backgrounds.
PrimeNG 21 Custom MultiSelect opens, filters, selects, removes chips, and uses its footer. Image overlay and reels carousels navigate. Checkboxes and radio styling hold.
ng-select 21 A representative select opens, filters, chooses, and clears with the existing theme.
ng-bootstrap 20 Any NgbModal and NgbActiveModal usage opens and closes without an injector error.
Voice circle recorder Recording starts, the visualizer animates with volume, and stopping produces a playable clip. This component's buffer type changed for TypeScript 5.9.
ngx-mask, ngx-toastr Masked inputs format as typed; toasts appear and dismiss.
Owl carousel Marketing carousels move both directions and respect breakpoints.
QR scanner, device detector The zxing scanner opens the camera where used; mobile and desktop detection still branch correctly.
Audio player An audio post loads, plays, pauses, seeks, and closes. This package still peers on Material 17.
IVS live video A live stream page loads the player. ng-ivs still peers on Angular 12.
Wallet and Stripe Balance and payout forms render. Test checkout and Connect complete against a safe environment.
Messages Conversation list and thread load; sending text and opening attachment UI do not throw.
ngrx store State-driven views update after actions; no Store provider error at startup.
HMR A template or style edit updates under ng serve.

Build and render one white-label tenant

  • Generate in a disposable working tree. generate-white-label-qa rewrites angular.json, package.json, src/index.html, and the white-label constants. Do not commit any of it.
  • Build a tenant with the QA configuration. WEBSITE=<tenant url> ng build <tenant> --configuration=qa --prerender=false with the 10 GB heap.
  • Serve dist/<tenant>/server/server.mjs. The home page title is the tenant's, not TopFan's, and server output has no runtime error.
  • Repeat the Firebase and phone input checks on the tenant. Tenant Firebase config and the patched phone input are the two places a tenant can diverge from TopFans.
  • Branding survives hydration. Logo, colors, login, and cover-page behaviour stay tenant-specific after JavaScript starts.

Known noise and deliberate holds

  • Lint still fails, as it did before. 890 findings remain from rules already in the v19 recommended set: prefer-standalone 541, template eqeqeq 67, no-output-on-prefix 50, and others. Two rules new in v20 recommended, prefer-inject and template/prefer-control-flow, are turned off with a comment because they are separate migrations.
  • Serverless main.js cannot be required in bare Node. It fails inside video.js's auto-setup, which needs window.setTimeout. This is unrelated to Angular and the target is not deployed anywhere.
  • Peer warnings from ng update. ng-ivs peers Angular 12, ngx-audio-player peers Material 17, primeng peered CDK 20 before its own bump. All compiled; both players are on the smoke matrix.
  • Control-flow migration was reverted. ng update rewrote 659 templates to @if and introduced four template errors. Templates stay on *ngIf.
  • generate-static-routes logs 400s. The QA backend rejects VIP-offer and store lookups for some creators. This is backend data, not a build failure, and the run still exits 0.
  • Sass deprecation warnings dropped from 288 to 226. Still noisy, still not blocking.
  • Local environment.ts and regenerated marketing constants are not committed. They are the developer's local QA switch and prefetch output respectively.

Stop the release if any of these fail

  • A foreground push produces zero or two notifications, or a fresh user cannot register a token.
  • The phone country dropdown does not open on signup, or the browser console shows forRoot is not a function.
  • A dropdown toggled from code stays open, or infinite scroll stops loading on the home feed.
  • A permitted host receives an empty <app-root> or server output reports an Angular runtime error.
  • Wallet checkout, Stripe Connect, audio playback, live video, or a representative white-label tenant regresses.