@estiva-app/conversation
The conversation rules of the Estiva SPEC §6.2–§6.8, for an app that would rather not implement them itself. Peek, Ship and estiva-agent read conversations through it.
A convenience, never a requirement. SPEC §9's conversation checks
(C10–C18) are written so an app can build conversations from the specification
alone, and this package's tests are those checks
(test/conformance.test.ts for C10–C16, test/membership.test.ts for C17 and C18, test/textMentions.test.ts for C28). If you find this package disagreeing with the
SPEC, the package is wrong.
npm install @estiva-app/conversation @estiva-app/protocol
@estiva-app/protocol is a peer. Nothing here opens a socket, holds a
credential or touches a browser global: the relay read is a QueryFn you
pass, the clock is a createdAtMs you pass, and drafts take their storage.
What is in it
| SPEC | exports | |
|---|---|---|
| comment or mention | §6.4 | isCommentOn, strengthOn, threadStrength, anchorsOf, isReply, namedInBody |
| threading | §6.4 Replies | parentOf, groupThreads |
| one shape for a message, a reply and a thread, every app's: a top-level message present, deleted or not returned | §6.3–§6.8 | foldThreads, Message, Thread |
| resolving a thread: the newest counts, in its message's Folder | §6.4 Resolving a thread, C51 | resolutionOf, resolutionsByTarget, resolutionTargetOf |
| where a message's lines draw: a link alone on its line is its file's row | §13.2, §13.6.2, C21 | placeLines, writtenUrlOf, urlNames |
| writing | §6.4, §13.6 | buildComment, buildReply, placeOf, referenceTagsFor, mentionTagsFor |
| what a composer's pick writes | §13.1 | mentionText, messageReference, urgentTagsFor, URGENT_TAG |
| what the shared comment box holds, as the message it writes, and back | §13.1 | composerText, composerDocument, withoutNewUrgency, composerDraft |
what a box shows under itself while a comment is written or edited: the one file's card (an edit's own card, with its ✕) and each block link's part, and the embed a send or an edit writes | §13.6.1, §13.6.2 | boxReadOf, boxLinksOf, embedsToSend, embedsToEdit, namedBefore, withoutCard, linkTargetOf |
what [ offers, and in what order: Files, then Messages, a message found by its own words | §13.1 | rankReferences, referenceSectionsOf, messageCandidate, referenceMatch, fileReference, referenceSearch, MESSAGE_TIER, FILE_TIER, REFERENCE_CAPS |
| a message as a preview line says it, a link as what it names; the words it is found by | §13.1, §13.6.2 | openingWords, referenceWord, messageWords |
| ordering | §6.2, §6.3 | trustedTs, orderingMs, byOrder |
| edits and attachments | §6.8 | editTargetOf, foldEdits, foldAttachments |
| reactions | §6.6 | REACTION_HORIZON, reactionHorizon, reactionTargetOf, reactionEmojiOf, foldReactions |
| the reactions to a person | §6.6, §11.8 | reactionsToMeFilter, reactionTakeBackFilter, keepReactionToMe, reactionsToMe, reactionStreamOf, newReactionsIn, reactionsSeenWrites |
| one read for all of the above | §6.6, §6.8 | decorationsOf |
| which controls to offer | §6.5 | offersEditAndDelete |
| drafts | — | createDraftStore, draftKeys |
| membership | §11.8 | membershipOf, membersOf, streamOf, membershipFilters, candidateFilesOf, buildMembershipChange |
| muting | §11.8 | parseMutedBlob, serializeMutedBlob, buildMutedEvent, mutedFilter, mutedFromFollowed |
| places and following | §11.8 Places and following | followingOf, placeKindOf, placeFilters, unreadOnPlace, isUnreadOnPlace, buildLevelChange, parseTeamsBlob, serializeTeamsBlob, buildTeamsEvent, teamsFilter |
| read state | §11.1–§11.6 | channelContext, fileContext, threadContext, loadSlotIdentity, advanceContexts, fetchReadState, publishReadState, createReadStateQueue, watchDwell, mergeSlots, effectiveReadAt, THREAD_RULE_FROM, REPLY_FLOOR_CONTEXT, capReadStateContexts, reactionsContext, REACTIONS_FLOOR_CONTEXT, reactionsSeenOf, isCarriedContextId |
| unread | §11.3, §11.8 | unreadIn, isUnread |
| a mention in a file's text | §11.8, §13.1 | mentionAddsFor, newlyNamedPeople, peopleNamedInText |
Chat, an edit, a reaction and a deletion are built with
@estiva-app/protocol's buildMessage, buildEdit, buildReaction and
buildDeletion.
Read state takes its environment like the rest: slot storage and randomness
(loadSlotIdentity), the relay query, the signer and NIP-44 (fetchReadState,
publishReadState). When to write can come from here too:
createReadStateQueue is the one queue a tab marks read through (the
debounce, the read-back of its own slot before every whole-blob publish, the
backoff, a change of person), and watchDwell marks what stayed on screen for
READ_DWELL_MS. Ship and Leaf use both; an app may still write its own.
Light a dot from the files you already load, not from discovery alone. A
file's membership comes from membershipOf over that file's own events, which
an app already holds for any file it shows. membershipFilters is for listing
"your files", and a busy person's results are cut short by the relay's limit
(each filter returned 2,000 events for one person in 90 days on 2026-10-01). A
dot built only on discovery would miss a file the limit dropped.
What is not in it
The views (CON-18), the composer's editor and its @, !@, [ and /
menus (@estiva-app/ui/editor, which writes through mentionText and
messageReference above), a Folder's roster (the relay's
kind:39002), and anything only one app needs.