Skip to content

Address review findings: bugs, memory leaks, CSS consolidation#10

Merged
gtandersen merged 2 commits into
gtandersen:geir/signup-screen-review-cleanfrom
runbox:piratefinn/signup-review-findings
Jun 9, 2026
Merged

Address review findings: bugs, memory leaks, CSS consolidation#10
gtandersen merged 2 commits into
gtandersen:geir/signup-screen-review-cleanfrom
runbox:piratefinn/signup-review-findings

Conversation

@piratefinn

Copy link
Copy Markdown

This branch applies review findings from PR runbox#1849. Two commits cover 23 fixes across 8 files: a critical domain-selector bug, five memory leak and timer bugs, validation and UX improvements, test hardening, and CSS consolidation.

Context

The signup page is a solid piece of work: lazy-loaded standalone module, runtime API config, native form POST to the Perl backend, and an intentional marketing-surface visual identity separate from the in-app Material theme. Three review passes found 23 issues; 19 were fixed in the first commit (dcb2fe25), 4 more in the refinement commit (86538deb), and 7 items remain deferred.

Critical

Domain selector rendered [object Object] in production. getRunboxDomains() declared Observable<string[]> but the backend returns {id, name}[]. Tests passed because mocks matched the (incorrect) signature. Fixed by mapping objects to strings at the consumer and correcting all mocks. The service signature fix is deferred (touches every consumer).

Memory leaks and timer bugs

  • setInterval leak on hCaptcha script error — polling never stopped after a failed script tag. Fixed with clearInterval in the error listener.
  • Unbounded setTimeout(0) retry in renderHCaptcha — survived component destroy, leaked memory. Fixed with destroyed flag and timer capture/clear in ngOnDestroy.
  • Async chain after destroy — subscriptions appended script to DOM after ngOnDestroy. Fixed with subs.add() teardown and post-await guard.
  • Stale timer cross-instance interference — timeout from instance A could remove the script element instance B was polling. Fixed with destroyed guard in catch handler.
  • Failed script stayed in DOM — caused polling leak on revisit. Fixed with .catch removal.

Validation and UX

  • Submit guard used hCaptchaSiteKey (set early) instead of hCaptchaReady (set after load), giving a misleading error. Fixed guard and disambiguated message.
  • formElement.submit() throwing left submitInProgress stuck. Fixed with try/catch and state reset.
  • "Complete the CAPTCHA" message shown when widget failed to load (not the user's fault). Fixed by clearing site key on failure.
  • Focus selector returned wrong element via querySelector alternation. Fixed with two-pass: Angular-invalid first, native-invalid second.
  • ASCII-only password regex excluded Unicode users. Fixed with \p{Ll}/u, \p{Lu}/u.
  • account_number forwarded without validation. Added /^[0-9]{0,12}$/ check before binding.
  • getSignupHCaptchaSiteKey could return undefined. Added res?.result ?? ''.

Cleanup

  • Dead SIGNUP_HCAPTCHA_SITE_KEY env var removed from gen-env.js.
  • Dead accountType hidden form field removed (backend ignores it).
  • 20 hardcoded hex literals replaced with SCSS palette tokens ($rmm-signup-slate-*, mat.get-color-from-palette($rmm-default-error)).
  • Footer gradient hex literals replaced with palette tokens matching the header.
  • Array.isArray guard added on domain list (folded into C1 fix).

CSS consolidation

The four-step breakpoint ladder (900/720/560/420px) repeated padding, width, and font-size overrides. Consolidated into fluid clamp() expressions on the base declarations — 35 lines net reduction (883 to 844 lines), no visual change at any viewport.

Change Expression
Hero/note horizontal padding clamp(0.9rem, calc(1.5vw + 0.45rem), 1.75rem)
Form horizontal padding clamp(0.9rem, calc(1.2vw + 0.45rem), 1.5rem)
Width gutter clamp(0.75rem, calc(2vw + 0.4rem), 2rem)
Hero text font-size clamp(0.92rem, calc(0.4vw + 0.84rem), 1.04rem)
Body copy font-size clamp(0.92rem, calc(0.4vw + 0.8rem), 1rem)

Tests

  • Mock domain shape corrected to {id, name}[] with explanatory comment
  • window.hcaptcha stub added in beforeEach, cleaned up in afterEach
  • "captcha missing" split into script-failed (system fault) vs user-skipped (user fault)
  • New "still loading" test with resolver pattern to prevent GC leak
  • afterEach uses try/finally for defensive cleanup
  • Cypress stub captures window.__hcaptchaParams (scaffolding for future callback tests)

254 pass, 0 fail, 2 skipped.

Backend verification

API contracts verified against RMM/perl/ source:

  • GET /rest/v1/signup/hcaptcha/site_key returns {status, result: '<key>'} (scalar). Endpoint is unauthenticated, which is correct for signup.
  • GET /rest/v1/runbox_domains returns {status, results: [{name, id}]} (array). Consumer maps objects to strings.
  • POST /signup/signup reads 20 form fields. Backend does not read type, accountType, or domain — dead accountType field removed.
  • Password validation: backend (Runbox::Core::Utils->check_password) is authoritative; client-side meter is advisory only.
  • Honeypot: phone_number_home silently blocks signup if filled. Correctly included as hidden field.

Architecture notes

The signup page is a marketing surface with its own visual identity. The 13 new SCSS palette tokens in _rmm-theme-vars.scss are correctly scoped: they produce zero CSS output, are consumed only by signup.component.scss via @use, and do not leak into the global stylesheet. The component uses default Emulated view encapsulation, so all styles are scoped to the component host regardless of file size.

Deferred follow-ups

Item Why
rbwebmail.ts return type fix Touches every consumer (aliases, profiles, recipients, singlemailviewer)
Interceptor skip for signup endpoints Touches shared auth code, needs allowlist tests
SRI on hCaptcha script hCaptcha rotates hashes frequently, needs update cadence decision
Material primitive swap (I3 tier B) Design call: signup intentionally differs from in-app Material look
CJK password scoring Design decision on scoring system (3/4 vs 3/5 character classes)
Cypress callback tests Stub is scaffolding-ready, tests not yet written
%signup-card SCSS placeholder Requires extracting shared properties across unrelated selectors

Original review (geir-signup-screen-review-clean):
- C1: Map getRunboxDomains objects to strings defensively; fix mock shapes
- I1: Clear interval on existing-script error listener
- I2: Add destroyed flag and timer cleanup in ngOnDestroy
- I3: Replace hardcoded hex literals with theme palette tokens
- S1: Remove dead SIGNUP_HCAPTCHA_SITE_KEY env var from gen-env.js
- S3: Clear hCaptchaSiteKey on script failure for accurate error message
- S4: Try/catch around formElement.submit() with state reset
- S5: Nullish-coalescing on getSignupHCaptchaSiteKey response
- S7: Remove dead hidden accountType form field
- S8: Unicode-aware password strength regex (\p{Ll}, \p{Lu})
- S9: Validate account_number query param before binding
- S11: Add afterEach fixture.destroy() and hcaptcha stub cleanup
- S12: Capture hCaptcha render params in Cypress stub
- S13: Two-pass focus selector (Angular-invalid first, native second)

Remediation pass (fix-signup-review-findings):
- N1: Gate submit guard on hCaptchaReady instead of hCaptchaSiteKey
- N2: Remove failed hCaptcha script element on error
- N3: Capture subscriptions and guard async chain on destroyed flag
- N4: Add 15s timeout on script-load polling for ad blocker/CSP scenarios
- N5: Wrap afterEach in try/finally for resilient test cleanup
- Guard destroyed-component state in hcaptcha catch handler (N6)
- Resolve paused promise in 'still loading' test to prevent GC leak (N9)
- Replace footer gradient hex literals with palette tokens
- Consolidate breakpoint padding/width/font-size overrides into clamp()
  expressions on base rules (35 lines net reduction, 883 -> 844 lines)
- Add consolidated review document covering all three review passes
@gtandersen gtandersen merged commit 3c97d57 into gtandersen:geir/signup-screen-review-clean Jun 9, 2026
5 checks passed
@piratefinn piratefinn deleted the piratefinn/signup-review-findings branch June 12, 2026 01:53
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