Skip to content

[Feature] Implement paginated post-captures loading with bounded image cache #3371

Description

@numbers-official

Summary

The DiaBackendAssetRepository fetches ALL post-captures in a single API request and caches viewed media Blobs in an unbounded in-memory Map. Combined with cascading re-creation of DetailedCapture objects, this causes escalating memory pressure, network latency, and UI jank as user collections grow.

Affected Files

Primary — Unbounded data fetching

  • src/app/shared/dia-backend/asset/dia-backend-asset-repository.service.ts
    • Lines 56-58: postCapturesImageCache$ — an unbounded Map<string, Blob> with no eviction
    • Lines 65-88: postCaptures$ — fetches total count, then makes a single request with limit: count to download ALL post-captures
    • Lines 131-148: getAndCachePostCaptureMedia$() — stores full Blob objects (multi-MB each) with no max size

Secondary — Cascading object re-creation

  • src/app/features/home/post-capture-tab/post-capture-tab.component.ts
    • Lines 24-33: Subscribes to postCaptures$, triggering the full unbounded load
  • src/app/features/home/details/details.page.ts
    • Lines 127-144: fromPostCaptures$ — re-creates ALL DetailedCapture instances (each with 12+ observable properties) on every upstream emission

Impact

  • Memory: Unbounded Blob cache grows to hundreds of MB for active users, causing OS-level app kills on memory-constrained devices
  • Network: Single large API request causes 5-15+ second delays on mobile connections
  • UI: DetailedCapture object re-creation causes Swiper re-rendering and GC pressure, resulting in janky scrolling
  • Backend: Single request for all assets puts unnecessary strain on the API server

Suggested Implementation

Phase 1: Paginated loading

Replace "fetch count then fetch all" with proper pagination:

private readonly PAGE_SIZE = 20;

fetchPostCaptures$(offset: number = 0) {
  return this.list$({
    orderBy: 'source_transaction',
    limit: this.PAGE_SIZE,
    offset,
  });
}

Follow the existing pattern in CaptureTabComponent which already uses capturedTabPageIndex$ with itemsPerPage = 10.

Phase 2: LRU cache eviction

Replace unbounded Map<string, Blob> with a bounded LRU cache (max ~20 entries):

private readonly MAX_CACHE_SIZE = 20;

private evictOldestIfNeeded(cache: Map<string, Blob>) {
  if (cache.size > this.MAX_CACHE_SIZE) {
    const oldestKey = cache.keys().next().value;
    if (oldestKey) cache.delete(oldestKey);
  }
}

Phase 3: Stabilize DetailedCapture object identity

Cache DetailedCapture objects by ID to prevent unnecessary re-creation:

private detailedCaptureCache = new Map<string, DetailedCapture>();

References

Generated by Health Monitor with Omni

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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