Skip to content

[Feature] Resilient engagement zone image loading with graceful degradation #84

@numbers-official

Description

@numbers-official

Summary

The preloadEngagementZoneImages() method in modal.ts uses a fail-fast strategy: if any single engagement zone image fails to load, the entire promise rejects and no engagement zones are ever displayed. This means one broken image URL silently disables all engagement zones for that session.

Affected Code

src/modal/modal.ts, lines 198–222:

private preloadEngagementZoneImages(): Promise<void> {
    return new Promise((resolve, reject) => {
      let loadedImages = 0;
      const imageUrls = ...;

      imageUrls.forEach((url) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
          loadedImages++;
          if (loadedImages === imageUrls.length) {
            this.handleImageLoad();  // Only called if ALL succeed
            resolve();
          }
        };
        img.onerror = () => {
          console.error(`Image failed to load: ${url}`);
          reject(new Error(`Image failed to load: ${url}`));  // Kills everything
        };
      });
    });
  }

Problem

  1. A user configures 5 engagement zone images; 1 URL is broken (expired CDN link, typo, etc.)
  2. The onerror handler fires and rejects the promise
  3. handleImageLoad() is never called → _imageLoaded stays false → shimmer placeholder shown forever
  4. The 4 perfectly valid engagement zones are never displayed
  5. No error feedback is visible to the end user or the host application

Suggested Implementation

Replace the all-or-nothing approach with a resilient loading strategy:

private async preloadEngagementZoneImages(): Promise<void> {
  const imageUrls = this._engagementZones.length > 0
    ? this._engagementZones.map((zone) => zone.image)
    : [Constant.url.defaultEngagementImage];

  const results = await Promise.allSettled(
    imageUrls.map((url) => new Promise<string>((resolve, reject) => {
      const img = new Image();
      img.src = url;
      img.onload = () => resolve(url);
      img.onerror = () => reject(new Error(`Failed: ${url}`));
    }))
  );

  // Filter engagement zones to only those with successfully loaded images
  const loadedUrls = new Set(
    results
      .filter((r): r is PromiseFulfilledResult<string> => r.status === 'fulfilled')
      .map((r) => r.value)
  );

  if (this._engagementZones.length > 0) {
    this._engagementZones = this._engagementZones.filter(
      (zone) => loadedUrls.has(zone.image)
    );
  }

  if (loadedUrls.size > 0) {
    this.handleImageLoad();
  }
}

Expected Impact

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions