Development notes

Make the messages page load fast

The inbox, the open thread, and the composer rules arrive in one response. The first visit downloads only the code that paints them.

What the page waits on

/messages lazy-loads MessageContainerComponent, then holds the thread behind msgrightLoader until a later call finishes. The messages are already in memory. The template hides them with *ngIf="messageList?.length && !msgrightLoader".

Step Where What it does
1 userProfileData Waits for the profile in the store before fetchInitialData runs.
2 api/get-chatGroups-and-users Loads every conversation. searchResults sorts the bucket again on each row.
3 setInitalDisplay Opens the first conversation and calls api/getpreviousMessagesFromHttp.
4 getUserEmail Waits 1 second, then calls api/chat-user-validation. The loader stays up until that returns.

getUserChatLogs also calls checkNewUserValidation before a conversation is selected, so validation often runs twice. Search is already debounced at 500 ms and is fine to leave.

That order is a client choice sitting on a read API that cannot answer the first screen. api/get-chatGroups-and-users returns every conversation and no messages. api/getpreviousMessagesFromHttp returns history and no composer rules. The client learns the groupId only after the list returns, unless it already stored one from a previous visit.

What the route downloads

The route chunk is the whole feature. About 4,000 lines of component and about 2,300 lines of template pull in libraries the first paint never uses.

  • Emoji picker from @ctrl/ngx-emoji-mart, imported even while the picker is closed.
  • IVS player from @cotufa82/ng-ivs, present for every video bubble.
  • Audio player and circle progress, present for every audio bubble and every upload.
  • Share modals for merchandise, portfolio, and event tickets, plus the automated-message modal and the image overlay. They sit in the template while closed.
  • Drag and drop from @angular/cdk/drag-drop, used only while pricing attachments.

After the page is up, ngAfterViewChecked calls detectChanges() on every cycle. The loops call getTimeDiff(), getCurrentMessages(), and getProfileURL() from the template. getTimeDiff() runs moment for each row. Every conversation and every loaded message stays in the DOM. Infinite scroll adds older messages and never drops them.

One read for the first screen

This is the change that shortens a cold load. Two parallel calls still wait for the list before they know which thread to open. One response can carry the list page, the open thread, and the composer rules.

GET api/get-chatGroups-and-users already takes search_key. Add tab, limit, a cursor, and an optional groupId. tab is featured, general, or broadcast. The cursor is last_message.timeStamp for a 1:1 row and updated_at for a message list.

Field What the page needs
conversations One page, already sorted. A 1:1 row needs groupId, users[0] name, username, gsi4pk, avatar, badges, featured and subscriber flags, last_message.text, last_message.type, last_message.timeStamp, updated_at, unread count, unread tip, and pending guaranteed reply. A list row needs list_name, list_icon, key, recipient count, and is_mailing_list.
counts Unread totals for Priority and General, so the tabs do not depend on downloading every row.
thread The first page of api/getpreviousMessagesFromHttp for groupId, or for the top conversation when groupId is omitted.
dmValidations The object api/chat-user-validation returns today for that thread. The composer reads freeDms, price_for_dm, currency_info, isReplyGuaranteed, isGuaranteedReplyPending, minTipKeys, and is_creator.

POST api/getpreviousMessagesFromHttp stays for older history and for opening a different row. The body is groupId, optional from for a broadcast, and optional timeStamp for the next older page. There is no limit. Cap each page. Return message as an object. The client runs JSON.parse on every row today.

Keep POST api/chat-user-validation for a deep link that skipped this read, and for a row switch when that row did not include dmValidations. The page should not call it before a conversation exists, and it should not wait a second after messages arrive.

Send, upload, tip, paid lock, broadcast, and automated-message writes stay on their current APIs.

Changes, in order

Show the thread when messages return

Read thread and dmValidations off the list response and paint the thread immediately. Until that response exists, drop the 1 second setTimeout in getUserEmail and turn msgrightLoader off when api/getpreviousMessagesFromHttp returns. Keep the composer disabled until dmValidations is present. Skip the validation call inside getUserChatLogs on the initial load.

Split the chunk

Keep the list and the open thread in the route. Load the rest when the user opens it, with @defer or a dynamic import().

  • Emoji picker, on the emoji control.
  • IVS player, when a video actually plays.
  • Audio player and circle progress, only on bubbles and uploads that need them.
  • Merchandise, portfolio, and event-ticket share modals, the automated-message modal, and the image overlay.

Stop the extra change detection

Remove detectChanges() from ngAfterViewChecked. Format timestamps when messages arrive, and bind the string. Give both *ngFors a trackBy. Sort each conversation bucket once after the loop in searchResults.

Paint less

Render only the active tab's page of conversations, and ask the server for the next cursor when the user scrolls the list. Keep the open thread to the latest page, and window anything older than what infinite scroll has fetched. Pass the last opened groupId on the list request so a return visit still opens that thread. If that id is missing, use the top conversation.

How to tell it worked

  1. A cold load of /messages is one read. The inbox page, the open thread, and dmValidations are in that response. The thread is visible when it arrives.
  2. A large inbox does not download every conversation. Scrolling the list requests the next cursor. Switching Priority, General, and Message Lists requests that tab.
  3. Scrolling up in a thread requests the next timeStamp page. The first page is capped.
  4. The composer stays disabled until dmValidations is present. Send, tip, guaranteed reply, and paid DM still follow today's rules.
  5. The messages chunk no longer includes emoji-mart, ng-ivs, or the three share modals on first load. Each of those loads on first use and still works.
  6. Scrolling a long thread does not hitch from a detectChanges() loop.
  7. A return visit passes the last groupId. A missing id opens the top conversation.

Leave these alone

  • Sending, uploads, tips, paid locks, broadcasts, and automated messages keep their current write APIs.
  • White-label DM rules and the under-18 composer stay as they are.
  • Search debounce stays at 500 ms.

Ship the combined read first. Then drop the artificial wait and split the chunk. The last two client steps keep the page fast after it is open.