Guides

Channels, forums, threads, and reactions

Membership-based channels and forums, nested threads via threadRootId, 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.

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.

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 not an allowlist. It defaults to ["πŸ‘","❀️","πŸŽ‰"].

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