Mail state — Implementation patterns (L3)
1. Mail-list rows
A row has this shape:
MailListRowState { rowKey, resourceRef, projection, sortKey, orderKey, pendingMarkers}rowKey is stable for the represented row across replacement snapshots while that resource remains the same row in the same projection kind. Message rows use message identity. Conversation rows use conversation identity and carry the current conversationToken inside their projection.
sortKey is opaque renderer data for preserving anchors and diagnostics. The renderer may send it back in a window request, but it must not compare sort keys to decide authoritative order. orderKey is optional display/debug metadata; it is not a second query evaluator.
pendingMarkers describe runtime-known local mutation effects for the row, such as pending read state, flag state, move/delete/archive, tag change, draft save, or send. Markers include the runtime mutation identity and settlement state when available.
2. Continuation and anchor state
Window continuation state is runtime-authored:
WindowContinuation { beforeCursor, afterCursor, hasBefore, hasAfter}Cursors are opaque and scoped to the QueryScope, projection kind, sort, and read watermark that produced them unless the runtime explicitly accepts them for a later window request.
The UI owns scroll position, viewport size, and pixel offsets. When it needs another window, it sends a WindowRequest such as initial, after cursor, before cursor, or around anchor. Anchor requests use a row key and optional opaque sort key from a previous runtime snapshot. If the anchor no longer exists or cannot be resolved, the runtime returns a replacement window with anchor status such as kept, moved, removed, or unknown.
The renderer may use anchor status to preserve or reset scroll position. It must not synthesize missing rows to preserve scroll.
3. Validation
3.1 State-constructor tests
Tests cover that HTTP message reads, live message assertions, catch-up assertions, and snapshots produce the same overlapping message fields and ConversationRef for the same authority-server state.
3.2 Query tests
Tests cover representative query predicates, including sender filters such as from:Alex, across source message lists, global search, smart mailboxes, conversation search, and automation preview where those surfaces support query text.
Tests also cover that the same query text under different base scopes produces separate authority-server requests/runtime views and cache keys, and that dependency analysis allows safe skip/recompute decisions for message, mailbox, smart-mailbox, and account assertions.
Runtime mail-list window tests cover row-key stability, opaque cursor scoping, window continuation, around-anchor requests, anchor removal, and pending row markers.
3.3 Conversation token tests
Tests cover that the conversation token changes when membership or envelope-visible aggregates change, and does not require renderers to compute a hash from loaded messages.
3.4 Message assertion tests
Tests cover create, update, destroy, body-token changes, attachment-token changes, and collapsed catch-up before/after behavior. Destroy events must retain the previous summary in before. Sync-path emitters (expunge / membership removal / full delete / mailbox cleanup) are pinned to carry projection and countDeltas like command writes, so rows and counts both update through the one assertion stream (L2 §7.1).
4. Assertions
| ID | Sev. | Assertion |
|---|---|---|
| mail-list-row-identity | MUST | Mail-list rows carry stable row keys and opaque runtime ordering metadata without making the renderer an order authority. |
| window-continuation-opaque | MUST | Window cursors and anchor sort keys are opaque runtime values scoped to the query/window context that produced them. |