Portal chats come in four types. Each type has distinct visibility, access, and moderation semantics.Documentation Index
Fetch the complete documentation index at: https://docs.7331.org/llms.txt
Use this file to discover all available pages before exploring further.
OFFICIAL
Admin-created chats that appear in the public directory and are visible to everyone — signed-in or not.- Owner: an admin user (appears in moderation checks)
- Visibility: listed in
GET /v1/portal/directory - Join: anyone can browse; signed-in users can chat
- Moderation: owner or anyone with
can_act_on(actor, owner)
PUBLIC
User-created chats with a vanity slug. Premium-only to create.- Owner: the creator (any PREMIUM/LIFETIME user)
- Invariant:
slug IS NOT NULL - Visibility: listed in
GET /v1/portal/directory - Join: anyone with the link; signed-in to chat
- Moderation: owner or
can_act_on(actor, owner)— chat type doesn’t restrict admin moderation
PRIVATE
Invite-only chats — not listed anywhere.- Owner: the creator (PREMIUM/LIFETIME)
- Access: owner + users who used a live invite code
- Join:
POST /v1/portal/resolve/{code}to join via an invite code - Moderation: owner only for config/invites/clipboard; owner OR ADMIN+ for delete/mute/kick (ADMIN override exists because we still need to reach behind the curtain for abuse reports)
DIRECT
Ephemeral 1-on-1 chats created by the matcher. Users don’t create these directly.- Owner:
NULL(the match system “owns” them) - Members: exactly two, pinned at creation
- Lifecycle:
- Matcher pairs two queued users → creates direct chat → both socket-joined
- Either user can call
POST /v1/portal/match/leaveor disconnect - Explicit leave → peer gets
room:peer_leftwithgrace_until_ms=0, chat destroyed immediately - Disconnect → 60s grace window for reconnect; chat destroyed on timeout
- Moderation: no per-chat mutes; global hard-mute still applies
- Encryption: same epoch rotation — a leave destroys the chat and purges keys
Shared fields
Every chat has:id— integer PK, used in all API pathsname— display name (not unique)chat_type— the four values aboveslug— nullable; required for PUBLICbrowser_config— JSONB;BrowserConfig(browser type, start URL, kiosk, adblock)chat_config— JSONB;RoomConfig(wipe preset, history visibility, control locks, …)
Chat vs browser separation
Chat and the neko browser are independent:- Chat viewer membership lives in
ConnectionTracker; you can join chat before the VM starts - VM membership lives in neko; controlled by
room:start/room:stop room:stopdoes NOT evict chat viewers — users keep chatting across VM restarts- Wipes rotate the epoch but don’t touch membership
