Threads and updates

A resource may contain many threads. Replies belong to the thread. The thread root and flat replies in the threads layout store parentCommentId: null. Those replies are siblings of the root, not children of it. A nested reply sets parentCommentId to the comment it answers. Depth 0 is top-level. Replies stop at depth 5. listComments returns that parent id and depth, oldest first. version starts at 1. activitySeq starts at 0 and becomes 1 on the root comment.

Threads list newest activity first: lastActivityAt DESC, then id DESC. lastActivityAt moves when a comment is created. Edits, resolve, reopen, and deletion do not move it. Comments list oldest first: createdAt ASC, then id ASC.

Cursors are opaque base64url JSON { "v": 1, "t": "<ISO-8601>", "id": "<row id>" }. Default page size is 50. limit is an integer from 1 through 100. Above 100 is validation_error. A short page returns nextCursor: null. The client dedupes by id. Load more stays available while nextCursor is non-null.

Filters all, open, resolved, and unread run before the limit. The stored body stays plain text. The widget renders a small inline subset: **bold**, _italic_ or *italic* beside a letter or digit, `code`, [label](https://…), and bare http or https URLs. Other schemes and raw HTML stay text. Anchors use rel="nofollow noopener". Mentions win when markdown would cross them.

Hosted subscribe opens GET /api/v1/stream for that tuple, with optional threadId. The publishable key and viewerToken travel on the query because EventSource cannot set an authorization header. The secret key is never in that query. Payload is ids only: threadId, commentId, eventId. Heartbeat is every 20 seconds. The stream closes at token expiry and on scope change.

If subscribe is omitted or the stream errors, the widget polls every 4 seconds, pauses while the document is hidden, and refreshes on focus and after a local write. Reconnect backoff is 4s, 8s, 16s, then 60s. Concurrent SSE listeners are capped at 20 per workspace per process. Several replicas do not share that cap.

Pending submits stay disabled until the request settles. On validation, conflict, rate limit, quota, forbidden, or a network failure, the composer text and mention chips stay. Optimistic rows roll back or show a failed state with retry.

Related: Limits · Unread · API overview