Guides

Channels, forums, threads, and reactions

Membership-based channels and forums, nested threads via threadRootId, structured mentions, and idempotent reactions.

GozWire keeps conversation access membership-based. Public discovery, ranking, votes, workspace provisioning, and product chrome are application concerns.

Channels and forums

await gozwire.conversations.createChannel({
  creatorId: "user-a",
  title: "engineering",
  userIds: ["user-b"],
});
await gozwire.conversations.createForum({
  creatorId: "user-a",
  title: "ask",
  userIds: ["user-b"],
});

Both kinds reuse the same participant roles as groups. Direct chats still cannot add participants. Outsiders cannot send, list history, or subscribe.

A blocked pair cannot be invited into a new group, channel, or forum. Errors match a missing identity. Members who already share a room may still send ordinary messages and thread replies.

Default retention for channel and forum is persistent only, with sender override off. A 24h or after-read send is rejected until the host expands retention.byType or an authorized participant updates conversation policy.

Nested threads

A top-level message is its own threadRootId. A reply copies the parent’s root, so nested replies stay in one tree.

  • messages.history({ rootsOnly: true }) lists posts (thread_root_id = id).
  • messages.thread({ threadRootId }) pages that tree with cursors. There is no SQL OFFSET.

Cross-conversation replyToId values are rejected. Invisible (held or blocked) messages cannot be replied to. A blocked pair in a shared room may still use replyToId on a visible message.

Mentions

Mentions are structured identity IDs in metadata.mentions, not a body-text parse. Autocomplete must call conversations.membersForMention for the current conversation. That list is active members excluding the actor. Persist still drops blocked IDs. See messaging.

Reactions

await gozwire.reactions.add({ messageId, userId, reaction: "πŸ‘" });
await gozwire.reactions.remove({ messageId, userId, reaction: "πŸ‘" });
await gozwire.reactions.list({ messageId, actorId });

Reactions are stored as (message_id, user_id, reaction). Duplicate adds return the original row. The engine accepts any valid bounded emoji or :custom: token. config.reactions.suggested is picker inventory, not an allowlist. It defaults to ["πŸ‘","❀️","πŸŽ‰"]. The only runtime editor is configuration.updateReactions({ suggested }).

What stays in the application

  • Reddit-style ranking, awards, and feeds
  • Discord or Slack channel directories and permissions beyond membership
  • Account signup, SSO, and profile bios
  • Custom emoji asset hosting