# Pinned and recent people

Document: SPEC-014. Revision: 0.7. Date: 2026-10-31. Lifecycle: D-071 requirements confirmed; technical completion under D-024/D-049. Supersedes: D-065's absence of a right sidebar only. D-194/SPEC-005 supersedes this document's right-sidebar/category placement for the native Linux successor; underlying saved-contact/Recent scopes remain active.

## Scope

D-194 selects a native Linux Direct Messages tree containing direct conversations and all pinned contacts, ordered by last message, newest pin and alphabetically under SPEC-005. This supersedes the right-sidebar/category placement below only for that successor; the released web/Electron/mobile clients retain their current presentation. Native tree removal/closure cannot implicitly delete contacts or protected Recent records, and an arbitrary Recent channel/voice encounter does not itself become a direct conversation or imply contact consent.

D-087 requests a successor with Message/Call actions and a third Server Members section; [SPEC-018](018-direct-conversations.md) and the approved D-090 sections in SPEC-005/008 implement that change in alpha.11. D-092 simplifies row controls to phone/message icons and removes Group/Channel buttons. D-147 renames the visible Friends category to Pinned, replaces hearts with thumbtacks and adds local private-activity indicators; SPEC-005 owns their presentation. The saved-contact API and synchronization are unchanged. This document owns the underlying address book and Recent data. The primary action changes from last-known-channel navigation to a DM; saved contacts remain one-way and do not themselves grant contact consent or reveal presence. Server Members is a server-wide directory, not a duplicate selected-channel roster.

R-084 restores a compact right sidebar. Pinned is a personal saved-contact list, without mutual requests or notifications to the saved person. The human selected account-wide Pinned and client-local Recent. SPEC-005 owns presentation, SPEC-008 owns authorization, and SPEC-003's device/session protections continue to apply. D-090/SPEC-018 add direct messaging without introducing global presence.

## Data and synchronization

D-194/D-195 implementation contract: a saved contact adds optional server-owned `pinnedAt`, a nonnegative safe integer in Unix milliseconds. The home sets it from its acceptance clock when an account first saves a person. Repeated saves or snapshot/profile updates retain the existing timestamp; removing and later saving creates a new timestamp. An older contact without the field remains undated, including on an idempotent save; do not invent a date from fetch, encounter or upgrade time. Ignore any client-supplied `pinnedAt` within a person snapshot and never accept it as authority. Responses retain the optional value for approved devices of the same account. Existing clients can omit it on saves without erasing a date. There is no configuration or user-editable override; absence means an undated legacy pin, not current time. SPEC-008 owns server enforcement. SPEC-005 specifies alphabetical fallback for legacy undated pins and keeps last-message recency independent of Recent observations.

A saved person contains `id`, `username`, `domain`, `nickname`, and optional last-known `room: {id,name}` context. New snapshots carry `destinationVersion:1`; direct encounters omit the channel instead of inventing one. Legacy snapshots with room context remain readable. Identity comes from the authoritative room roster, not a client-supplied display name. The qualified domain and durable account ID identify the person; a nickname is only a label. Store a snapshot, not an ongoing subscription to someone's profile or location. Listing a saved person must not reveal subsequent private-room membership or online state.

SPEC-015 extends last-known rooms with optional `server: {id,domain,baseUrl,name}` so identical room IDs on different servers cannot navigate to the wrong place. For foreign encounters the trusted client takes the snapshot from the verified selected host's roster and saves it to its home address book. The home accepts a bounded, validated `{person, saved}` snapshot in addition to the legacy local `{target, room, saved}` action, deduplicated by domain plus account UUID. This is user-owned address-book data; it is not home-attested evidence that a channel relationship exists. Neither an address-book snapshot nor a server label is a device trust pin, live-presence claim or membership grant. The home never contacts an arbitrary URL merely to save a friend. Removal by qualified person remains possible after leaving that server. Existing local actions retain their server-derived roster check. This additive contract is selected under D-049 to preserve account-wide Pinned across the explicitly requested multiple servers without exporting a destination bearer token to the home server.

The authenticated `GET api/contacts` returns `{contacts: Person[]}` for the signed-in account only. `POST api/contacts` accepts exactly `{target: string, room: string, saved: boolean}`. To add, require current admission for both actor and target in the named accessible ordinary channel; reject self, disabled/unknown targets and hidden thread rooms. For removal, require only that the entry belongs to the actor; a departed contact remains removable. Sets/removals are idempotent per target, atomically persisted, and return the current list. No client can name another list owner or overwrite the entire array. Concurrent actions on different targets preserve both changes; same-target actions follow server acceptance order. The standard authenticated request limits apply.

`api/info` advertises `contacts/1` after implementing this contract. `api/sync` includes the current account's `contacts` independently of selected-room admission. Older servers without this capability leave the feature unavailable with an explanation; no false success or cross-account fallback. The generic records table stores `contacts` keyed by actor ID; no schema downgrade changes are required because the records grant no authorization.

Pinned is private from other users, including administrators using ordinary client APIs. The home operator can inspect its stored metadata; this increment does not claim E2EE for the address book. It contains no passwords, private keys or message text. Device revocation and account disablement stop access through existing session checks. Removing a friend deletes the saved entry; it neither blocks that person nor revokes channel access.

Recent records verified message authors encountered when opening/reading a channel and people sharing the client's active voice call. Mere channel membership, an unselected room roster and remote-device voice do not count. Exclude self and failed/unverified text. Keep each person's latest encounter, qualified identity, last-known channel and encounter time. Deduplicate by domain plus account ID; text uses the message's accepted timestamp, voice uses observation time. Refresh voice observations at most once per minute. Opening older history cannot replace a newer encounter. Clear recent records a local cutoff so old loaded messages cannot immediately repopulate the list; a later call encounter or new message can appear. Recent never becomes proof of identity trust, current membership or online status.

D-094 hides saved friends from the displayed Recent list and its count using the full domain/account identity, without removing their stored encounters. Server Members remains unfiltered by friendship. Removing a friend reveals an otherwise eligible Recent encounter; Clear still clears all Recent history, including encounters currently hidden by friendship. SPEC-005 places existing encounter context below the action icons.

Keep Recent inside the current account's protected vault. Account switches cannot expose another account's list. Old profiles lacking the fields start empty, and profile activation clears absent optional state. Device linking/recovery does not export Recent. Removing a person from Pinned does not implicitly erase independent Recent history; Clear recent does not remove Pinned.

## Configuration

No new deployment or user configuration. Fixed implementation bounds are 100 saved contacts per account, 50 recent people per account/client, and 30 days of Recent retention, measured against local wall time and pruned on observation/render. Contacts persist until removal. These are completion choices under D-024/D-049, not individually stated human preferences. Reject a new contact at the limit; updating/removing an existing contact still works. Empty lists are the initial state. Recent fields are optional additive protected-vault fields; invalid entries are ignored rather than interpreted as identity or navigation authority.

The sidebar is expanded on desktop. At narrow widths, an ordinary disclosure below the main conversation exposes the same people sections (including Server Members under SPEC-018); it is transient view state, initially collapsed. No modal, backdrop or focus trap. Fixed snapshots must still show full qualified addresses via the identity label and cannot manufacture an online indicator.

## Testing

D-194's native combined-tree/order tests belong to SPEC-005. The pin-chronology extension tests authoritative timestamps, idempotent pinning, unpin/re-pin behavior, cross-device consistency, rejection of caller timestamp authority and legacy/older-client compatibility; no date may be invented for existing records. Native presentation changes must leave current address-book/Recent API and data tests passing.

Server unit/integration coverage verifies account isolation, authenticated reads/writes, authorized target snapshots, self/unknown/hidden-room denial, idempotency, limits, cross-device retrieval, concurrent independent updates and removal after lost room access. Client units cover domain-qualified deduplication, own-account exclusion, timestamp ordering, retention/limit boundaries, clear cutoff and account-profile isolation. Browser checks verify the people sidebar groups, add/remove through participant menus and Recent, persistence/reload, accessible icon labels and saved-pin state, stacked identity/actions/context, friend exclusion from Recent with directory inclusion and reappearance after unfriend, no voice join on message navigation, narrow disclosure, Back placement and keyboard focus. Existing device and room permission tests remain applicable.

## Acceptance criteria

- New saved contacts receive the home acceptance time; repeated saves preserve it, removal/re-pin creates a new time, and undated legacy contacts remain undated. Caller-supplied dates cannot change either case.

- For the D-194 native Linux successor, all saved contacts remain in the Direct Messages tree independently of open tabs/history, with SPEC-005's order; the predecessor's right-sidebar/category placement no longer applies on that client. Existing saved-contact consent/privacy, account scope and protected Recent retention remain intact.
- An approved second client for the same account sees an added/removed friend; another account cannot read or change that list.
- Pinned appears above Recent with compact rows and full identities; saved friends are excluded from Recent rows/counts but remain eligible in Server Members. Removing a friend can reveal an eligible stored encounter; clearing Recent while a person is saved prevents that encounter from reappearing. No implied presence or duplicate selected-channel member roster.
- Only verified authors from displayed history and participants in this client's active call enter Recent; other room rosters and failed messages do not.
- Recent survives a reload on that account/client, excludes another account's entries, respects its limits, and is not sent in network requests or device-link copies.
- Clearing Recent leaves Pinned intact and suppresses previously loaded messages until a new eligible encounter.
- Under SPEC-005/D-092, name/message-icon activation opens the canonical DM without joining voice, phone actions retain contact acceptance, and sidebar rows have no Group or Channel action. Pin state agrees across Pinned, Recent and Server Members.
- Back is at the top-left of all shared workspaces and the sidebar remains usable at narrow widths without horizontal page overflow.
