Development notes

Chat cursor pagination

The inbox no longer downloads every conversation and sorts them into tabs on the client. Each tab asks the server for its own page. History stopped using a timestamp and started using a cursor. The first history page also brings validation, so ChatUserValidation is gone.

t3code/explain-chat-api-pagination message-container + messages.constant

What changed

Almost all of it is message-container. Query strings and body keys live in messages.constant.ts so the component is not inventing tab=featured inline.

BeforeNow
One GET ?search_key=, then split by is_featured / is_broadcast / empty last message ?tab=featured|general|broadcast|all&counts=true. The current tab owns data. No local split.
Creators and fans both parsed the full mix. Fans concatenated Featured + General into one list. Creators fetch the open tab. Fans send tab=all and still render one list.
Tab badges were unread sums of whatever was already loaded Badges come from counts.featured and counts.general. Opening a thread subtracts that row's unread from the stored counts.
Sidebar unread came from the badge-count endpoint, then drifted as you opened threads Each inbox response sets the sidebar to general + featured + broadcast
Left list had no next page Scroll down sends the last cursor. Null cursor stops.
History POST sent timeStamp. onUp() was commented out. First page is { groupId, include_validation }. Next page is { groupId, cursor }. Scroll-up prepends the older page.
Permissions were a second POST to ChatUserValidation First history page returns validation. Retry refetches that first page. Broadcast and followers still skip it.

Inbox request

Creators map Priority โ†’ featured, General โ†’ general, Message Lists โ†’ broadcast. Fans never send those. They send all.

GET api/get-chatGroups-and-users?tab=featured&counts=true
GET api/get-chatGroups-and-users?tab=featured&counts=true&cursor=<from last response>
GET api/get-chatGroups-and-users?tab=all&counts=true&search_key=ana

Empty search is omitted, not sent as search_key=. A tab change or a new search string drops the cursor and replaces that tab's rows. Switching away and back also starts page 1 again. That matches the API. Do not keep a stale page 2 sitting under a new query.

buildChatInboxUrl is a plain function. First page, next page, and a fan search look like this when I ran it outside Angular:

api/get-chatGroups-and-users?tab=featured&counts=true
api/get-chatGroups-and-users?tab=featured&counts=true&search_key=ana&cursor=abc%2B%2F%3D
api/get-chatGroups-and-users?tab=all&counts=true

Base64 cursors go through URLSearchParams, so + and / do not break the query.

Trust the tab

The old searchResults walk was the whole product rule for Priority vs General. It also dropped conversations with an empty last_message. That is how a server row vanished after a paginated tab=featured response. The new path maps each item through the existing badge normalizer, then writes the array into the open tab. Featured rows stay in Featured even if is_featured is missing on the payload.

Counts

Tab pills are still unread, not conversation totals. Message Lists never had a pill count and still does not. The sidebar number is the sum of the three keys. Search responses overwrite those numbers with whatever the server sent. If search returns filtered unread, the pills will shrink until you clear the box. That is the server talking, not a local guess.

Opening a thread still zeros that row and subtracts its unread from the sidebar the way it used to. It also decrements the matching key on the stored counts object so the Priority / General pills do not jump back up until the next inbox GET.

History and validation

First open of a DM:

{
  "groupId": "CHATGROUP#...",
  "include_validation": true
}

The reply is { response, cursor, validation }. response becomes messageList. validation becomes dmValidations. Same post-processing as the old call: guaranteed-reply pending, tip modal, validateNewUser. If include_validation was sent and validation is missing, send stays locked and Retry refetches the first page.

Scroll toward the top. If cursor is still there, the next POST is only groupId plus that cursor. Older messages are prepended and the viewport offset is restored so you do not snap to the newest bubble. Broadcast still sends from. It does not ask for validation.

getUserEmail and checkNewUserValidation were deleted from this component. Retry no longer hits api/chat-user-validation.

What I checked

Local :4200 after generate-white-label-staging, copy of environment.rudeus-greyrat.staging.ts onto environment.ts, then cleanup-white-label. The app compiled. The Rudeus white-label homepage loaded. /messages redirected to /auth/login. I did not have a password, so I never saw Priority, a cursor, or include_validation on the wire.

Leave environment.ts out of the commit. That copy is only for this machine. The Vite overlay on wls-config.json 500 is the missing generated file after cleanup. It is not this change.

CheckResult
ng serve compile Passed after renaming the unused selectedUser arg. 3.5s incremental rebuild.
Rudeus staging env on :4200 Title is Official Website of Rudeus Greyrat. Sidebar is SIGN IN / CREATE NEW ACCOUNT.
/messages logged out Auth guard sent me to the login form. No inbox GET. Correct.
URL builder First page, cursor+search, and blank search match the contract. Cursor is encoded.
Logged-in inbox, tabs, scroll, history cursor Not run. Need the Rudeus password or another staging creator.

Verification checklist

Creator first. Then a fan. DevTools Network, disable cache. Watch get-chatGroups-and-users and getpreviousMessagesFromHttp. There should be no chat-user-validation on a normal DM open.

0 of 0 checked

High Inbox first page

High Inbox next page and search

High History and validation

Medium Broadcast, fans, leftovers