Make messages load faster

First remove the one-second wait. Then load less code and request less data. Backend changes offer the biggest potential gain for large inboxes.

Code audit · 30 September 2026 · Snapshot c36ff481f5 plus working tree. No app changes, live timings, bundle measurements, or backend query inspection. Priorities are estimates.

Where the wait comes from

Guards + lazy route → user profile → inbox → selected history → 1-second timer → permissions → visible messages

getUserEmail() schedules that timer. The template hides history while msgrightLoader is true. Other callbacks also change this flag, so the visible delay varies.

The route already uses loadChildren and NoPreloading. Another lazy route alone will not shrink its dependencies. White-label targeting, username redirects, new chats, broadcasts, and tip-back links add user-lookup or group-creation branches.

Frontend fixes, in priority order

  1. Separate reading from sending. Remove the timer; split inbox, history, and permission loading. Fetch history and permissions together when the participant ID is known. Show history immediately; enable send, tip, and purchase only after validation. On validation failure, keep authorized history readable, disable actions, and offer retry. The server still authorizes operations.
  2. Fix cancellation before parallelizing. History and validation share userValidationCancel$ and can cancel each other. Give each selected conversation independent child requests. Switching A → B cancels A; only B's responses may update B. Search already debounces 500 ms, but also needs distinct queries and cancellation of in-flight older searches.
  3. Defer optional features. Keep inbox, text history, and basic composer ready. Load emoji, recording, players, attachments, automated-message editing, and share dialogs on interaction. Their modules and modal classes are statically imported; the constructor resolves upload/recording services and loads video CSS.
  4. Create real import boundaries. Use standalone wrappers with @defer or dynamic dialog imports. @if only hides UI. Parent references and view queries can retain eager dependencies; required overlay queries must tolerate deferred creation. Move S3, Compressor, and RecordRTC imports to their action paths. Delaying service construction alone does not help. Check shared chunks before claiming byte savings. Angular rules.
  5. Stop repeated list work. searchResults() sorts after every insertion: partition, then sort once per category. getCurrentMessages() allocates arrays during template checks: select the active array when state changes. Track conversations by group ID and messages by server ID, preserving stable optimistic IDs until acknowledgement. Current tracking uses object identity.
  6. Remove the extra view check. ngAfterViewChecked() calls detectChanges(). Fix dependent updates before removing it. Split rows, history, and composer; use immutable inputs or signals. Adjust in-place mutations before applying OnPush. CPU savings need profiling. Angular guidance.
  7. Finish history pagination. Requests accept a timestamp, but onUp()'s fetch is commented out. Add cursor, loading, end, error, and duplicate-page handling. Preserve the first visible message when prepending. Scroll down only on initial open or when already near the bottom. Replace 500 ms scroll timers with Angular render-driven updates.
  8. Limit media and rendered rows. Offscreen videos should use posters and load playback on demand; they currently preload metadata. Many photos are already lazy-loaded, so inspect actual requests. Virtualize the conversation list if still expensive. Defer variable-height history virtualization until needed: media, date groups, and scroll anchors complicate it.
  9. Reuse bounded recent state. Paint cached inbox/thread data, then revalidate and merge. Scope it to tenant, account, permissions, tab, query, and conversation. Clear on logout or identity changes. Reconcile edits, deletions, read receipts, and permission changes.
  10. Fix cache semantics first. GETs use ApiCacheService; POST history stays on ApiCallService. Current cache keys contain only the endpoint. REFRESH_EVERY_N counts requests, not time, and emits cached data once while separately refreshing the store. Add identity scoping and live updates. ALWAYS_FRESH adds no caching. After splitting optional code, prefetch on navigation intent rather than preloading every route.

Backend changes worth doing

These are proposed contracts. Confirm storage, indexes, query counts, and latency first.

  1. Page conversation summaries. Evolve get-chatGroups-and-users or version it. Accept tab, search, limit, and opaque cursor; start with 30 configurable rows. Return ID, name, avatar thumbnail, preview, activity time, unread count, classification, and essential participant fields. Fetch full broadcast membership on demand.
  2. Preserve inbox behavior. Return total tab counts independently of loaded rows. Preserve featured-tip classification, general/bulk categories, creator/fan rules, and white-label visibility. Default-thread selection must not change because a page is truncated.
  3. Maintain conversation summaries. A per-user read model can avoid rebuilding an inbox from message history. Update it for messages, reads, tips, membership, and classification. Query by tenant/user and activity, with conversation ID breaking ties. If traces show per-row participant queries, batch them or store the small display fields in summaries.
  4. Combine history and permissions. A thread response should include metadata, 30–50 recent messages, older cursor, and permission version. Return structured message JSON; the client currently parses a nested JSON string per message.
  5. Choose the shortest entry path. Normal entry: inbox returns default thread ID, then fetch that thread. Direct link: fetch thread independently of inbox. Include default history in the inbox response only if round-trip latency warrants it. Never block inbox paint on optional enrichment or every thread's history.
  6. Do not just bundle slow calls. Remove duplicate work and parallelize independent server reads. One endpoint running old queries serially can remain slow. Authorize reads; recheck current payment/send rules even when the response includes cached permissions.
  7. Use stable history cursors. Query by conversation, timestamp, and unique message ID; handle equal timestamps and bound page size. Deduplicate pages and websocket messages by ID. If DynamoDB is confirmed, verify keys and use continuation tokens. Frontend field names do not prove the database. AWS pagination.
  8. Recover realtime changes. Add event IDs or sequence versions. Reconnect from the last acknowledged position; fetch a fresh first page if it expires. Merge into summaries instead of reloading the inbox. Add bounded server caching only for measured repeated work, with tenant/user/permission scoping and invalidation. Never publicly CDN-cache private conversations.
  9. Move uploads off the reading path. Short-lived upload authorizations and direct HTTP uploads can remove the S3 client dependency. Preserve progress, cancellation, multipart uploads, thumbnails, and processing status. Do this later unless upload code dominates the bundle.

Measure outcomes, not the spinner

Time usable inbox, readable history, and enabled composer separately. Skeletons do not count as content. The targets below are provisional, not measured gains.

CheckTarget / evidence
Fixed waitNo 1-second gap before validation. Verify with network traces and performance marks.
HistoryPaint within 100 ms of response processing on the agreed device, independently of validation. Trace and record slow-permission cases.
Warm returnCached inbox within 200 ms after route activation; refresh in background. Test with route code already loaded.
Cold entryProvisional p75 below 1.5 s on agreed mobile/network conditions. Run at least 20 comparable trials plus production measurements; revise from baseline.
Server workBound row counts. Trace p50/p95 handler/query time, compressed bytes, and database reads; redact captures.
JavaScriptNo optional media/emoji/recording chunks before use unless required elsewhere. Compare production build stats, cold waterfalls, transfer and parse cost.
  • Test empty/small/large inboxes, long mixed-media threads, direct links, slow validation, fast conversation switches, overlapping searches, reconnects, and incoming messages.
  • Cover fans, creators, delegates, paid/blocked DMs, broadcasts, mailing lists, automation, tip-back links, and new chats. Test TopFans and a generated white-label project.
  • Assert correct-thread history, disabled sends while permissions are unknown, stable pagination scroll, and private-cache clearing on account/tenant changes.
  • Benchmark production builds with --max_old_space_size=10048, not development-server timings.

Ship in this order

  1. Baseline → timer, loader, and cancellation fixes. Small-to-medium scope. Use shared skeletons matching the actual layout.
  2. Optional imports → sorting, stable IDs, and view checks. Medium scope. Keep SCSS, shared UI, constant-backed text, and AlertModal-based new dialogs.
  3. Paginated APIs → counts, cursors, thread permissions. Medium-to-large scope. Feature-flag, backfill summaries, compare old/new responses, and retain rollback during migration.
  4. Warm cache → realtime reconciliation → selective prefetch. Medium scope; keep identity isolation.
  5. Release by cohort. Compare p75/p95, missing messages, send failures, duplicate events, unread-count errors, and server load before expanding.

Code references

Paths below refer to the audited snapshot. MC means src/app/modules/messages/components/message-container/message-container.component.

FindingLocation
Route / entry / delaysrc/app/app-routing.module.ts:150; src/app/modules/messages/messages.routes.ts; MC.ts lines 521, 675, 1423, 3008, 3080, 3118; MC.html 725–731.
Shared cancellationMC.ts 3030–3034, 3121–3125.
Eager importsMC.ts 16–203, 418–431; src/app/services/upload.service.ts:1; src/app/modules/shared/services/audio-recording.service.ts:1.
Repeated rendering workMC.ts 761–802, 2647, 2898, 3882; MC.html 265–267, 745–748.
Pagination / mediaMC.ts 3026–3029, 3492–3505; MC.html 929, 1561.
Cache limitationssrc/app/core/cache/api-cache.service.ts, lines 37–91, 142–186.