CHANNELS IMPLEMENTATION PLAN - nself-org/nchat GitHub Wiki
Channels and Communities Implementation Plan
Version: 0.9.1 Status: Planning Phase Tasks Covered: TODO.md Tasks 60-65 (Phase 6) Last Updated: 2026-02-03
Table of Contents
- Executive Summary
- Platform Research Summary
- Channel Types (Task 60)
- Channel CRUD
- Categories/Sections (Task 61)
- Discord-Style Servers (Task 62)
- Telegram/WhatsApp Structures (Task 63)
- Broadcast Lists (Task 64)
- Channel Permissions (Task 65)
- Database Schema
- API Endpoints
- Real-time Events
- UI Components
- Implementation Phases
- Testing Requirements
- Migration Strategy
Executive Summary
This document provides a comprehensive implementation plan for channels and communities in nchat (nself-chat), achieving feature parity with Slack, Discord, Telegram, and WhatsApp while maintaining a unified, flexible architecture.
Goals
- Support all major channel types (public, private, DM, group DM, broadcast)
- Implement Discord-style server/guild hierarchy
- Support Telegram-style channels, groups, and supergroups
- Support WhatsApp-style communities with sub-groups
- Granular permission system with role and user-level overrides
- Real-time synchronization across all clients
Existing Infrastructure
The codebase already has foundational types and components:
/src/types/channel.ts- Core channel type definitions/src/stores/channel-store.ts- Zustand store for channel state/src/lib/rbac/channel-permissions.ts- Permission management/src/lib/channels/channel-categories.ts- Category definitions/src/graphql/mutations/channels.ts- GraphQL mutations
Platform Research Summary
Slack Conversations API
- Unified API: Single
conversations.*API handles all channel-like entities - Channel Types: public_channel, private_channel, mpim (group DM), im (DM)
- Channel IDs: Prefix indicates type (C=channel, G=group, D=DM)
- Workspaces: Enterprise Grid supports multi-workspace channels
- Permissions: Scoped by type (channels:read, groups:read, mpim:read, im:read)
Reference: Slack Conversations API
Discord Guilds/Servers
- Guild Hierarchy: Guild > Categories > Channels > Threads
- Channel Types: Text, Voice, Announcement, Stage, Forum
- Permission Overwrites: 53-bit integer with bitwise operations
- Hierarchy: Channel(User) > Channel(Role) > Channel(@everyone) > Server(@everyone) > Server(Role)
- Categories: Channels can inherit or override category permissions
Reference: Discord Permissions
Telegram Channels & Groups
- Basic Groups: Up to 200 members, shared message ID sequence
- Supergroups: Up to 200,000 members, separate message sequence
- Gigagroups: No member limit, admin-only posting
- Channels: One-way broadcast, admin posting only
- Forums: Supergroups split into distinct topics
Reference: Telegram API Channels
WhatsApp Communities
- Structure: Community > Announcement Group + Sub-groups
- Limits: 100 sub-groups, 2,000 members total, 1,024 per sub-group
- Channels: One-way broadcast, unlimited followers
- Broadcast Lists: Up to 256 contacts, requires mutual contact
Reference: WhatsApp Communities
1. Channel Types (Task 60)
1.1 Type Definitions
// Extended channel type enum
export enum ChannelType {
// Standard channels
PUBLIC = 'public', // Anyone can join
PRIVATE = 'private', // Invite only
// Direct messaging
DIRECT = 'direct', // 1:1 DM
GROUP_DM = 'group_dm', // Multi-user DM (up to 10)
// Broadcast types
BROADCAST = 'broadcast', // One-way announcements
ANNOUNCEMENT = 'announcement', // Read-only except admins
// Special types
VOICE = 'voice', // Voice-only channel
STAGE = 'stage', // Presentation/event channel
FORUM = 'forum', // Topic-based discussions
THREAD = 'thread', // Message thread
}
// Channel subtype for more granular control
export enum ChannelSubtype {
STANDARD = 'standard',
SUPERGROUP = 'supergroup', // Telegram-style large group
GIGAGROUP = 'gigagroup', // Telegram-style admin-only
COMMUNITY_ANNOUNCEMENT = 'community_announcement',
NEWS = 'news', // Discord-style news channel
}
1.2 Channel Interface
export interface Channel {
id: string
workspaceId: string // Server/workspace this belongs to
parentId?: string // For threads: parent channel
categoryId?: string // Category grouping
name: string
slug: string
description?: string
topic?: string
type: ChannelType
subtype?: ChannelSubtype
// Visibility & Access
visibility: 'visible' | 'hidden' | 'archived'
isPrivate: boolean
isNsfw: boolean
isReadOnly: boolean
isDefault: boolean // Auto-join for new members
// Media & Display
icon?: string
color?: string
banner?: string
// Ordering
position: number
// Ownership
ownerId: string
createdBy: string
createdAt: Date
updatedAt: Date
// Archive state
isArchived: boolean
archivedAt?: Date
archivedBy?: string
// Stats (denormalized)
memberCount: number
messageCount: number
lastMessageAt?: Date
lastMessageId?: string
// Settings
settings: ChannelSettings
// Permission template (null = inherit)
permissionSyncId?: string
}
1.3 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Define extended ChannelType enum | P0 | 2h | None |
| Update Channel interface | P0 | 4h | ChannelType |
| Create channel type validators | P0 | 2h | Channel interface |
| Build channel type icons/labels | P1 | 2h | None |
| Implement type-specific behaviors | P1 | 8h | Validators |
| Add broadcast channel restrictions | P1 | 4h | Type behaviors |
2. Channel CRUD
2.1 Create Channel
interface CreateChannelInput {
workspaceId: string
name: string
type: ChannelType
// Optional
description?: string
topic?: string
categoryId?: string
icon?: string
isPrivate?: boolean
isDefault?: boolean
settings?: Partial<ChannelSettings>
// For DMs
memberIds?: string[]
// Template to copy settings from
templateId?: string
}
interface CreateChannelResult {
channel: Channel
membership: ChannelMember
}
Validation Rules:
- Name: 1-100 chars, no excessive spaces
- Slug: Auto-generated, unique within workspace
- Type-specific: DM requires 2 members, Group DM requires 2-10
- Permission check: user must have
channel.createpermission
2.2 Update Channel
interface UpdateChannelInput {
channelId: string
name?: string
description?: string
topic?: string
icon?: string
color?: string
categoryId?: string
position?: number
isPrivate?: boolean
isReadOnly?: boolean
isNsfw?: boolean
settings?: Partial<ChannelSettings>
}
Restrictions:
- Cannot change channel type (except supergroup promotions)
- Cannot change DM participants (create new instead)
- Slug regeneration on name change (optional)
2.3 Archive/Delete Channel
// Soft delete - preserves history
interface ArchiveChannelInput {
channelId: string
reason?: string
}
// Hard delete - removes all data
interface DeleteChannelInput {
channelId: string
confirmPhrase: string // Must match channel name
}
Deletion Policy:
- DMs: Hide from list, preserve messages (per user)
- Regular channels: Archive first, delete after 30 days
- Cascade: Delete messages, memberships, invites, pins
2.4 Membership Management
interface ChannelMember {
channelId: string
userId: string
// Role within channel
role: 'owner' | 'admin' | 'moderator' | 'member' | 'guest'
// Timestamps
joinedAt: Date
lastReadAt?: Date
lastReadMessageId?: string
// User preferences
notificationLevel: 'all' | 'mentions' | 'none'
isMuted: boolean
mutedUntil?: Date
isPinned: boolean
// Read state
unreadCount: number
unreadMentionCount: number
// Optional
nickname?: string
customPermissions?: ChannelPermissionOverride
}
2.5 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Create channel mutation | P0 | 4h | DB schema |
| Update channel mutation | P0 | 4h | Create channel |
| Archive channel mutation | P0 | 2h | Update channel |
| Delete channel mutation | P1 | 4h | Archive channel |
| Join/leave channel | P0 | 4h | Membership schema |
| Add/remove members | P0 | 4h | Join/leave |
| Update member role | P1 | 2h | Add/remove |
| Bulk member operations | P2 | 4h | Member CRUD |
3. Categories/Sections (Task 61)
3.1 Category Structure
interface ChannelCategory {
id: string
workspaceId: string
name: string
description?: string
icon?: string
color?: string
// Ordering
position: number
// UI state (per-user)
isCollapsed?: boolean
// Permissions
defaultPermissions?: ChannelPermissionFlags
permissionOverrides?: ChannelPermissionOverride[]
// Metadata
createdAt: Date
updatedAt: Date
}
3.2 Category Features
Ordering:
- Categories have positions (0-indexed)
- Channels have positions within categories
- Drag-and-drop reordering
- Channels without category go to "Uncategorized"
Collapsible Sections:
- Per-user collapse state (stored in localStorage + synced)
- Show unread badge on collapsed category
- Keyboard navigation support
Permission Inheritance:
- Channels can "sync" with category permissions
- Sync status:
synced | not_synced - Sync toggle in channel settings
- Breaking sync on permission edit shows warning
3.3 Default Categories
const DEFAULT_CATEGORIES = [
{ id: 'general', name: 'General', icon: 'MessageSquare', position: 0, isDefault: true },
{ id: 'announcements', name: 'Announcements', icon: 'Megaphone', position: 1 },
{ id: 'teams', name: 'Teams', icon: 'Users', position: 2 },
{ id: 'projects', name: 'Projects', icon: 'FolderKanban', position: 3 },
{ id: 'support', name: 'Support', icon: 'HelpCircle', position: 4 },
{ id: 'social', name: 'Social', icon: 'Coffee', position: 5 },
{ id: 'archived', name: 'Archived', icon: 'Archive', position: 99, isSystem: true },
]
3.4 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Category CRUD mutations | P0 | 4h | DB schema |
| Category reordering API | P0 | 2h | CRUD |
| Move channel to category | P0 | 2h | CRUD |
| Per-user collapse state | P1 | 4h | User preferences |
| Permission inheritance | P1 | 6h | Permissions system |
| Category unread badges | P1 | 4h | Read state tracking |
| Drag-and-drop UI | P1 | 8h | Categories/channels |
4. Discord-Style Servers (Task 62)
4.1 Server/Workspace Concept
Discord's "Server" = nchat "Workspace"
interface Workspace {
id: string
// Identity
name: string
slug: string
description?: string
icon?: string
banner?: string
splash?: string // Invite splash image
// Ownership
ownerId: string
createdAt: Date
updatedAt: Date
// Settings
settings: WorkspaceSettings
// Discovery
isPublic: boolean
isDiscoverable: boolean // Listed in server discovery
discoverySplash?: string
vanityUrl?: string
// Verification
verificationLevel: 'none' | 'low' | 'medium' | 'high' | 'very_high'
explicitContentFilter: 'disabled' | 'members_without_roles' | 'all_members'
// Stats
memberCount: number
onlineCount: number
boostCount: number
boostTier: 0 | 1 | 2 | 3
// Limits
maxMembers: number
maxChannels: number
maxRoles: number
}
interface WorkspaceSettings {
defaultChannelId?: string
rulesChannelId?: string
systemChannelId?: string
// Notification settings
systemChannelFlags: number
// Features
features: WorkspaceFeature[]
// Premium
premiumTier: 'free' | 'starter' | 'pro' | 'enterprise'
premiumSubscribersCount: number
}
type WorkspaceFeature =
| 'COMMUNITY'
| 'DISCOVERABLE'
| 'VANITY_URL'
| 'BANNER'
| 'ANIMATED_ICON'
| 'WELCOME_SCREEN'
| 'NEWS_CHANNELS'
| 'FORUM_CHANNELS'
| 'PRIVATE_THREADS'
4.2 Server Discovery
interface WorkspaceDiscoveryCard {
workspaceId: string
name: string
description: string
icon?: string
splash?: string
memberCount: number
onlineCount: number
categories: string[] // Discovery categories
tags: string[]
primaryLanguage?: string
isPartnered: boolean
isVerified: boolean
}
interface WorkspaceInvite {
code: string
workspaceId: string
channelId?: string // Specific channel to join
createdBy: string
createdAt: Date
expiresAt?: Date
maxUses?: number
uses: number
isTemporary: boolean // Grant temporary membership
targetType?: 'stream' // For stream invites
}
4.3 Server Roles
interface WorkspaceRole {
id: string
workspaceId: string
name: string
color?: string
icon?: string
unicodeEmoji?: string
position: number // Hierarchy position
// Permission bitfield
permissions: bigint
// Display settings
isHoisted: boolean // Show separately in member list
isMentionable: boolean
// Special flags
isDefault: boolean // @everyone role
isManaged: boolean // Managed by integration
// Limits
memberCount: number
createdAt: Date
updatedAt: Date
}
4.4 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Workspace model & schema | P0 | 8h | None |
| Workspace CRUD API | P0 | 8h | Model |
| Workspace settings | P1 | 6h | CRUD |
| Server discovery API | P2 | 8h | Settings |
| Discovery UI | P2 | 12h | Discovery API |
| Invite system | P1 | 8h | Workspace model |
| Role management | P0 | 12h | Workspace model |
| Role permissions | P0 | 8h | Role management |
| Boost system | P3 | 16h | Billing integration |
5. Telegram/WhatsApp Structures (Task 63)
5.1 Telegram-Style Groups
// Promotion path: BasicGroup -> Supergroup -> Gigagroup
interface TelegramStyleSettings {
// Group type flags
isSupergroup: boolean // >200 members capability
isGigagroup: boolean // Admin-only posting
isForum: boolean // Topic-based
// Limits
memberLimit: number // 200 | 200000 | unlimited
// Features
hasLinkedChannel: boolean
linkedChannelId?: string
// Slow mode
slowModeDelay: number // seconds
// Admin features
signMessages: boolean // Show admin name
hasProtectedContent: boolean
hasAggressiveAntiSpam: boolean
}
Group Types:
- Basic Group: Up to 200 members, basic features
- Supergroup: Up to 200,000 members, advanced features
- Gigagroup: Unlimited members, admin-only posting
- Forum: Supergroup with topic threads
Promotion Flow:
BasicGroup (type: 'group')
↓ (auto at 200 members OR manual)
Supergroup (type: 'group', subtype: 'supergroup')
↓ (manual, one-way)
Gigagroup (type: 'group', subtype: 'gigagroup')
5.2 Telegram-Style Channels
interface BroadcastChannel extends Channel {
type: 'broadcast'
// Broadcast-specific
subscriberCount: number
isPublic: boolean
// Discussion group
discussionGroupId?: string
// Monetization (future)
isPremium: boolean
subscriptionPrice?: number
}
Features:
- One-way communication (admin posts only)
- Unlimited subscribers
- Optional discussion group linkage
- Message signatures (show which admin posted)
5.3 WhatsApp-Style Communities
interface Community {
id: string
workspaceId: string
name: string
description?: string
icon?: string
// Announcement channel (auto-created)
announcementChannelId: string
// Sub-groups
groupIds: string[]
maxGroups: number // Default: 100
// Stats
totalMemberCount: number
maxMembers: number // Default: 2000
// Settings
settings: CommunitySettings
createdBy: string
createdAt: Date
updatedAt: Date
}
interface CommunitySettings {
// Who can add groups
addGroupsPermission: 'admin' | 'member'
// Member permissions
membersCanInvite: boolean
// Moderation
approvalRequired: boolean
// Events
eventsEnabled: boolean
}
Community Structure:
Community
├── Announcement Channel (one-way, admin only)
├── Sub-Group 1 (two-way chat)
├── Sub-Group 2 (two-way chat)
└── Sub-Group N (up to 100)
5.4 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Supergroup promotion logic | P1 | 8h | Channel types |
| Gigagroup restrictions | P2 | 4h | Supergroup |
| Forum topics | P2 | 16h | Threads |
| Broadcast channel type | P1 | 8h | Channel types |
| Discussion group linking | P2 | 6h | Broadcast |
| Community model | P1 | 8h | Workspace |
| Community CRUD | P1 | 8h | Model |
| Announcement channel | P1 | 4h | Community CRUD |
| Sub-group management | P1 | 8h | Community |
6. Broadcast Lists (Task 64)
6.1 Broadcast List Model
interface BroadcastList {
id: string
workspaceId: string
name: string
description?: string
icon?: string
// Creator
ownerId: string
createdAt: Date
updatedAt: Date
// Subscribers
subscriberIds: string[]
subscriberCount: number
maxSubscribers: number // Default: 256 (WhatsApp), configurable
// Settings
settings: BroadcastListSettings
// Stats
totalMessagesSent: number
lastBroadcastAt?: Date
}
interface BroadcastListSettings {
// Who can subscribe
subscriptionMode: 'open' | 'invite' | 'admin'
// Delivery settings
allowReplies: boolean // If true, replies go to creator
showSenderName: boolean
// Analytics
trackDelivery: boolean
trackReads: boolean
}
6.2 Broadcast Message
interface BroadcastMessage {
id: string
broadcastListId: string
content: string
attachments?: Attachment[]
sentBy: string
sentAt: Date
// Delivery tracking
deliveryStats: {
total: number
delivered: number
read: number
failed: number
}
// Schedule
scheduledFor?: Date
isScheduled: boolean
}
6.3 Subscriber Management
interface BroadcastSubscriber {
broadcastListId: string
userId: string
subscribedAt: Date
subscribedBy?: string // If invited
// Preferences
notificationsEnabled: boolean
// Status
status: 'active' | 'unsubscribed' | 'blocked'
unsubscribedAt?: Date
}
6.4 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Broadcast list model | P1 | 4h | None |
| Create broadcast list | P1 | 4h | Model |
| Subscriber management | P1 | 6h | Create list |
| Send broadcast message | P1 | 8h | Subscribers |
| Delivery tracking | P2 | 8h | Send message |
| Scheduled broadcasts | P2 | 6h | Jobs plugin |
| Analytics dashboard | P3 | 12h | Tracking |
| Unsubscribe flow | P1 | 4h | Subscribers |
7. Channel Permissions (Task 65)
7.1 Permission System Design
Permission Resolution Order (Discord-style):
- Channel user override (highest priority)
- Channel role overrides
- Channel @everyone override
- Category permissions (if synced)
- Workspace role permissions
- Workspace @everyone permissions (lowest)
7.2 Permission Flags
// 53-bit permission bitfield (BigInt for safety)
export const CHANNEL_PERMISSIONS = {
// View
VIEW_CHANNEL: 1n << 0n,
READ_MESSAGE_HISTORY: 1n << 1n,
// Messages
SEND_MESSAGES: 1n << 2n,
SEND_MESSAGES_IN_THREADS: 1n << 3n,
EMBED_LINKS: 1n << 4n,
ATTACH_FILES: 1n << 5n,
ADD_REACTIONS: 1n << 6n,
USE_EXTERNAL_EMOJIS: 1n << 7n,
USE_EXTERNAL_STICKERS: 1n << 8n,
MENTION_EVERYONE: 1n << 9n,
MENTION_ROLES: 1n << 10n,
// Threads
CREATE_PUBLIC_THREADS: 1n << 11n,
CREATE_PRIVATE_THREADS: 1n << 12n,
// Voice
CONNECT: 1n << 13n,
SPEAK: 1n << 14n,
VIDEO: 1n << 15n,
USE_SOUNDBOARD: 1n << 16n,
USE_VOICE_ACTIVITY: 1n << 17n,
PRIORITY_SPEAKER: 1n << 18n,
MUTE_MEMBERS: 1n << 19n,
DEAFEN_MEMBERS: 1n << 20n,
MOVE_MEMBERS: 1n << 21n,
// Moderation
MANAGE_MESSAGES: 1n << 22n,
MANAGE_THREADS: 1n << 23n,
MANAGE_CHANNEL: 1n << 24n,
// Special
SEND_VOICE_MESSAGES: 1n << 25n,
SEND_POLLS: 1n << 26n,
USE_APPLICATION_COMMANDS: 1n << 27n,
} as const
type ChannelPermission = keyof typeof CHANNEL_PERMISSIONS
7.3 Permission Override
interface ChannelPermissionOverride {
id: string
channelId: string
// Target
targetType: 'role' | 'user'
targetId: string
// Permission changes (null = inherit)
allow: bigint // Explicitly allowed
deny: bigint // Explicitly denied
// Metadata
createdAt: Date
createdBy: string
expiresAt?: Date // Temporary overrides
}
7.4 Permission Calculation
function calculateEffectivePermissions(
userId: string,
channelId: string,
context: PermissionContext
): bigint {
// 1. Start with workspace @everyone permissions
let permissions = context.workspace.everyonePermissions
// 2. Apply workspace role permissions (OR together)
for (const role of context.userRoles) {
permissions |= role.permissions
}
// 3. Apply category permissions (if channel synced)
if (context.channel.permissionSyncId && context.category) {
// Category @everyone
const categoryEveryone = context.category.overrides.find(
(o) => o.targetType === 'role' && o.targetId === '@everyone'
)
if (categoryEveryone) {
permissions &= ~categoryEveryone.deny
permissions |= categoryEveryone.allow
}
// Category role overrides
for (const role of context.userRoles) {
const override = context.category.overrides.find(
(o) => o.targetType === 'role' && o.targetId === role.id
)
if (override) {
permissions &= ~override.deny
permissions |= override.allow
}
}
}
// 4. Apply channel @everyone override
const channelEveryone = context.channel.overrides.find(
(o) => o.targetType === 'role' && o.targetId === '@everyone'
)
if (channelEveryone) {
permissions &= ~channelEveryone.deny
permissions |= channelEveryone.allow
}
// 5. Apply channel role overrides
for (const role of context.userRoles) {
const override = context.channel.overrides.find(
(o) => o.targetType === 'role' && o.targetId === role.id
)
if (override) {
permissions &= ~override.deny
permissions |= override.allow
}
}
// 6. Apply channel user override (highest priority)
const userOverride = context.channel.overrides.find(
(o) => o.targetType === 'user' && o.targetId === userId
)
if (userOverride) {
permissions &= ~userOverride.deny
permissions |= userOverride.allow
}
return permissions
}
7.5 Default Permission Templates
const PERMISSION_TEMPLATES = {
// Standard member
member: {
allow:
VIEW_CHANNEL |
READ_MESSAGE_HISTORY |
SEND_MESSAGES |
EMBED_LINKS |
ATTACH_FILES |
ADD_REACTIONS,
deny: 0n,
},
// Read-only (announcements)
readonly: {
allow: VIEW_CHANNEL | READ_MESSAGE_HISTORY,
deny: SEND_MESSAGES,
},
// Moderator
moderator: {
allow: MANAGE_MESSAGES | MANAGE_THREADS,
deny: 0n,
},
// Voice participant
voice_participant: {
allow: CONNECT | SPEAK,
deny: 0n,
},
// Muted user
muted: {
allow: VIEW_CHANNEL | READ_MESSAGE_HISTORY,
deny: SEND_MESSAGES | ADD_REACTIONS | CONNECT | SPEAK,
},
}
7.6 Implementation Tasks
| Task | Priority | Effort | Dependencies |
|---|---|---|---|
| Permission bitfield implementation | P0 | 4h | None |
| Permission override schema | P0 | 4h | Bitfield |
| Permission calculation engine | P0 | 8h | Override schema |
| Server-side permission checks | P0 | 8h | Calculation |
| Client-side permission cache | P1 | 6h | Calculation |
| Permission editor UI | P1 | 12h | Calculation |
| Temporary permission overrides | P2 | 4h | Override schema |
| Permission audit log | P2 | 6h | All above |
8. Database Schema
8.1 Core Tables
-- Workspaces/Servers
CREATE TABLE nchat_workspaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identity
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
icon_url TEXT,
banner_url TEXT,
splash_url TEXT,
-- Ownership
owner_id UUID NOT NULL REFERENCES nchat_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Discovery
is_public BOOLEAN NOT NULL DEFAULT FALSE,
is_discoverable BOOLEAN NOT NULL DEFAULT FALSE,
vanity_url VARCHAR(50) UNIQUE,
-- Verification
verification_level SMALLINT NOT NULL DEFAULT 0,
explicit_content_filter SMALLINT NOT NULL DEFAULT 0,
-- Settings (JSONB for flexibility)
settings JSONB NOT NULL DEFAULT '{}',
features TEXT[] NOT NULL DEFAULT '{}',
-- Stats (denormalized)
member_count INTEGER NOT NULL DEFAULT 0,
channel_count INTEGER NOT NULL DEFAULT 0,
-- Limits
max_members INTEGER NOT NULL DEFAULT 500000,
max_channels INTEGER NOT NULL DEFAULT 500,
max_roles INTEGER NOT NULL DEFAULT 250
);
CREATE INDEX idx_workspaces_owner ON nchat_workspaces(owner_id);
CREATE INDEX idx_workspaces_discoverable ON nchat_workspaces(is_discoverable) WHERE is_discoverable = TRUE;
-- Workspace Roles
CREATE TABLE nchat_workspace_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
color VARCHAR(7),
icon_url TEXT,
unicode_emoji VARCHAR(8),
position INTEGER NOT NULL DEFAULT 0,
permissions BIGINT NOT NULL DEFAULT 0,
is_hoisted BOOLEAN NOT NULL DEFAULT FALSE,
is_mentionable BOOLEAN NOT NULL DEFAULT FALSE,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_managed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(workspace_id, name)
);
CREATE INDEX idx_roles_workspace ON nchat_workspace_roles(workspace_id);
CREATE INDEX idx_roles_position ON nchat_workspace_roles(workspace_id, position);
-- Channel Categories
CREATE TABLE nchat_channel_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
icon VARCHAR(50),
color VARCHAR(7),
position INTEGER NOT NULL DEFAULT 0,
-- Default permissions for channels in this category
default_permissions BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(workspace_id, name)
);
CREATE INDEX idx_categories_workspace ON nchat_channel_categories(workspace_id);
CREATE INDEX idx_categories_position ON nchat_channel_categories(workspace_id, position);
-- Channels
CREATE TABLE nchat_channels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
category_id UUID REFERENCES nchat_channel_categories(id) ON DELETE SET NULL,
parent_id UUID REFERENCES nchat_channels(id) ON DELETE CASCADE, -- For threads
-- Identity
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
topic TEXT,
-- Type
type VARCHAR(20) NOT NULL DEFAULT 'public',
subtype VARCHAR(30),
-- Visibility
is_private BOOLEAN NOT NULL DEFAULT FALSE,
is_nsfw BOOLEAN NOT NULL DEFAULT FALSE,
is_read_only BOOLEAN NOT NULL DEFAULT FALSE,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
archived_at TIMESTAMPTZ,
archived_by UUID REFERENCES nchat_users(id),
-- Display
icon VARCHAR(100),
color VARCHAR(7),
banner_url TEXT,
-- Ordering
position INTEGER NOT NULL DEFAULT 0,
-- Ownership
owner_id UUID NOT NULL REFERENCES nchat_users(id),
created_by UUID NOT NULL REFERENCES nchat_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Permission sync
permission_sync_id UUID REFERENCES nchat_channel_categories(id),
-- Stats (denormalized)
member_count INTEGER NOT NULL DEFAULT 0,
message_count INTEGER NOT NULL DEFAULT 0,
last_message_at TIMESTAMPTZ,
last_message_id UUID,
-- Settings
settings JSONB NOT NULL DEFAULT '{}',
UNIQUE(workspace_id, slug)
);
CREATE INDEX idx_channels_workspace ON nchat_channels(workspace_id);
CREATE INDEX idx_channels_category ON nchat_channels(category_id);
CREATE INDEX idx_channels_parent ON nchat_channels(parent_id);
CREATE INDEX idx_channels_type ON nchat_channels(workspace_id, type);
CREATE INDEX idx_channels_position ON nchat_channels(category_id, position);
-- Channel Members
CREATE TABLE nchat_channel_members (
channel_id UUID NOT NULL REFERENCES nchat_channels(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES nchat_users(id) ON DELETE CASCADE,
-- Role within channel
role VARCHAR(20) NOT NULL DEFAULT 'member',
-- Timestamps
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_read_at TIMESTAMPTZ,
last_read_message_id UUID,
-- Preferences
notification_level VARCHAR(20) NOT NULL DEFAULT 'all',
is_muted BOOLEAN NOT NULL DEFAULT FALSE,
muted_until TIMESTAMPTZ,
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
-- Read state
unread_count INTEGER NOT NULL DEFAULT 0,
unread_mention_count INTEGER NOT NULL DEFAULT 0,
-- Optional customization
nickname VARCHAR(32),
PRIMARY KEY (channel_id, user_id)
);
CREATE INDEX idx_channel_members_user ON nchat_channel_members(user_id);
CREATE INDEX idx_channel_members_joined ON nchat_channel_members(channel_id, joined_at);
-- Channel Permission Overrides
CREATE TABLE nchat_channel_permission_overrides (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES nchat_channels(id) ON DELETE CASCADE,
target_type VARCHAR(10) NOT NULL, -- 'role' | 'user'
target_id UUID NOT NULL,
allow_permissions BIGINT NOT NULL DEFAULT 0,
deny_permissions BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES nchat_users(id),
expires_at TIMESTAMPTZ,
UNIQUE(channel_id, target_type, target_id)
);
CREATE INDEX idx_permission_overrides_channel ON nchat_channel_permission_overrides(channel_id);
CREATE INDEX idx_permission_overrides_target ON nchat_channel_permission_overrides(target_type, target_id);
-- Channel Invites
CREATE TABLE nchat_channel_invites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(20) NOT NULL UNIQUE,
workspace_id UUID REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
channel_id UUID REFERENCES nchat_channels(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES nchat_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
max_uses INTEGER,
uses INTEGER NOT NULL DEFAULT 0,
is_temporary BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE INDEX idx_invites_code ON nchat_channel_invites(code) WHERE is_active = TRUE;
CREATE INDEX idx_invites_workspace ON nchat_channel_invites(workspace_id);
CREATE INDEX idx_invites_channel ON nchat_channel_invites(channel_id);
-- Communities (WhatsApp-style)
CREATE TABLE nchat_communities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
icon_url TEXT,
-- Auto-created announcement channel
announcement_channel_id UUID NOT NULL REFERENCES nchat_channels(id),
-- Settings
settings JSONB NOT NULL DEFAULT '{}',
-- Limits
max_groups INTEGER NOT NULL DEFAULT 100,
max_members INTEGER NOT NULL DEFAULT 2000,
-- Stats
total_member_count INTEGER NOT NULL DEFAULT 0,
group_count INTEGER NOT NULL DEFAULT 0,
created_by UUID NOT NULL REFERENCES nchat_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_communities_workspace ON nchat_communities(workspace_id);
-- Community Groups (Sub-groups)
CREATE TABLE nchat_community_groups (
community_id UUID NOT NULL REFERENCES nchat_communities(id) ON DELETE CASCADE,
channel_id UUID NOT NULL REFERENCES nchat_channels(id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
added_by UUID NOT NULL REFERENCES nchat_users(id),
PRIMARY KEY (community_id, channel_id)
);
CREATE INDEX idx_community_groups_channel ON nchat_community_groups(channel_id);
-- Broadcast Lists
CREATE TABLE nchat_broadcast_lists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES nchat_workspaces(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
icon VARCHAR(100),
owner_id UUID NOT NULL REFERENCES nchat_users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Settings
subscription_mode VARCHAR(20) NOT NULL DEFAULT 'admin',
allow_replies BOOLEAN NOT NULL DEFAULT FALSE,
show_sender_name BOOLEAN NOT NULL DEFAULT TRUE,
track_delivery BOOLEAN NOT NULL DEFAULT TRUE,
track_reads BOOLEAN NOT NULL DEFAULT TRUE,
-- Limits
max_subscribers INTEGER NOT NULL DEFAULT 256,
-- Stats
subscriber_count INTEGER NOT NULL DEFAULT 0,
total_messages_sent INTEGER NOT NULL DEFAULT 0,
last_broadcast_at TIMESTAMPTZ
);
CREATE INDEX idx_broadcast_lists_workspace ON nchat_broadcast_lists(workspace_id);
CREATE INDEX idx_broadcast_lists_owner ON nchat_broadcast_lists(owner_id);
-- Broadcast Subscribers
CREATE TABLE nchat_broadcast_subscribers (
broadcast_list_id UUID NOT NULL REFERENCES nchat_broadcast_lists(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES nchat_users(id) ON DELETE CASCADE,
subscribed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
subscribed_by UUID REFERENCES nchat_users(id),
notifications_enabled BOOLEAN NOT NULL DEFAULT TRUE,
status VARCHAR(20) NOT NULL DEFAULT 'active',
unsubscribed_at TIMESTAMPTZ,
PRIMARY KEY (broadcast_list_id, user_id)
);
CREATE INDEX idx_broadcast_subscribers_user ON nchat_broadcast_subscribers(user_id);
CREATE INDEX idx_broadcast_subscribers_status ON nchat_broadcast_subscribers(broadcast_list_id, status);
-- Broadcast Messages
CREATE TABLE nchat_broadcast_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
broadcast_list_id UUID NOT NULL REFERENCES nchat_broadcast_lists(id) ON DELETE CASCADE,
content TEXT NOT NULL,
attachments JSONB,
sent_by UUID NOT NULL REFERENCES nchat_users(id),
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Schedule
scheduled_for TIMESTAMPTZ,
is_scheduled BOOLEAN NOT NULL DEFAULT FALSE,
-- Delivery stats
total_recipients INTEGER NOT NULL DEFAULT 0,
delivered_count INTEGER NOT NULL DEFAULT 0,
read_count INTEGER NOT NULL DEFAULT 0,
failed_count INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_broadcast_messages_list ON nchat_broadcast_messages(broadcast_list_id);
CREATE INDEX idx_broadcast_messages_scheduled ON nchat_broadcast_messages(scheduled_for) WHERE is_scheduled = TRUE;
8.2 RLS Policies
-- Workspace visibility
CREATE POLICY workspace_visibility ON nchat_workspaces
FOR SELECT
USING (
is_public = TRUE
OR owner_id = auth.uid()
OR EXISTS (
SELECT 1 FROM nchat_workspace_members
WHERE workspace_id = nchat_workspaces.id
AND user_id = auth.uid()
)
);
-- Channel visibility (based on permissions)
CREATE POLICY channel_visibility ON nchat_channels
FOR SELECT
USING (
-- User is a member
EXISTS (
SELECT 1 FROM nchat_channel_members
WHERE channel_id = nchat_channels.id
AND user_id = auth.uid()
)
-- Or it's a public channel and user is workspace member
OR (
NOT is_private
AND EXISTS (
SELECT 1 FROM nchat_workspace_members
WHERE workspace_id = nchat_channels.workspace_id
AND user_id = auth.uid()
)
)
);
-- Channel member operations
CREATE POLICY channel_member_read ON nchat_channel_members
FOR SELECT
USING (
user_id = auth.uid()
OR EXISTS (
SELECT 1 FROM nchat_channel_members cm
WHERE cm.channel_id = nchat_channel_members.channel_id
AND cm.user_id = auth.uid()
)
);
9. API Endpoints
9.1 Channel Endpoints
// REST API Routes (or GraphQL equivalents)
// Channel CRUD
POST /api/channels // Create channel
GET /api/channels/:id // Get channel
PATCH /api/channels/:id // Update channel
DELETE /api/channels/:id // Delete channel
POST /api/channels/:id/archive // Archive channel
// Channel Discovery
GET /api/channels // List accessible channels
GET /api/channels/search // Search channels
// Membership
POST /api/channels/:id/join // Join channel
POST /api/channels/:id/leave // Leave channel
GET /api/channels/:id/members // List members
POST /api/channels/:id/members // Add member
DELETE /api/channels/:id/members/:userId // Remove member
PATCH /api/channels/:id/members/:userId // Update member role
// Invites
POST /api/channels/:id/invites // Create invite
GET /api/channels/:id/invites // List invites
DELETE /api/invites/:code // Revoke invite
POST /api/invites/:code/use // Use invite
// Permissions
GET /api/channels/:id/permissions // Get permissions
PUT /api/channels/:id/permissions // Update permissions
POST /api/channels/:id/permissions/sync // Sync with category
9.2 Category Endpoints
// Category CRUD
POST /api/categories // Create category
GET /api/categories // List categories
PATCH /api/categories/:id // Update category
DELETE /api/categories/:id // Delete category
PUT /api/categories/reorder // Reorder categories
// Channel ordering
PUT /api/categories/:id/channels/reorder // Reorder channels
POST /api/channels/:id/move // Move to category
9.3 Workspace Endpoints
// Workspace CRUD
POST /api/workspaces // Create workspace
GET /api/workspaces/:id // Get workspace
PATCH /api/workspaces/:id // Update workspace
DELETE /api/workspaces/:id // Delete workspace
// Discovery
GET /api/workspaces/discover // Discover public workspaces
GET /api/workspaces/:id/preview // Preview workspace (before joining)
// Roles
GET /api/workspaces/:id/roles // List roles
POST /api/workspaces/:id/roles // Create role
PATCH /api/workspaces/:id/roles/:roleId // Update role
DELETE /api/workspaces/:id/roles/:roleId // Delete role
PUT /api/workspaces/:id/roles/reorder // Reorder roles
9.4 Community Endpoints
// Community CRUD
POST /api/communities // Create community
GET /api/communities/:id // Get community
PATCH /api/communities/:id // Update community
DELETE /api/communities/:id // Delete community
// Groups
POST /api/communities/:id/groups // Add group
DELETE /api/communities/:id/groups/:groupId // Remove group
PUT /api/communities/:id/groups/reorder // Reorder groups
9.5 Broadcast Endpoints
// Broadcast Lists
POST /api/broadcast-lists // Create list
GET /api/broadcast-lists // List my broadcast lists
GET /api/broadcast-lists/:id // Get list
PATCH /api/broadcast-lists/:id // Update list
DELETE /api/broadcast-lists/:id // Delete list
// Subscribers
GET /api/broadcast-lists/:id/subscribers // List subscribers
POST /api/broadcast-lists/:id/subscribers // Add subscriber
DELETE /api/broadcast-lists/:id/subscribers/:userId // Remove subscriber
// Messages
POST /api/broadcast-lists/:id/send // Send broadcast
GET /api/broadcast-lists/:id/messages // List messages
GET /api/broadcast-lists/:id/messages/:msgId/stats // Get delivery stats
10. Real-time Events
10.1 Channel Events
// Socket.io / Realtime Plugin Events
// Channel lifecycle
'channel:created' // { channel: Channel, workspace_id }
'channel:updated' // { channel_id, updates, previous }
'channel:deleted' // { channel_id, workspace_id }
'channel:archived' // { channel_id, archived_by, reason }
'channel:unarchived' // { channel_id, unarchived_by }
// Membership
'channel:member_joined' // { channel_id, user_id, member }
'channel:member_left' // { channel_id, user_id }
'channel:member_updated' // { channel_id, user_id, updates }
'channel:member_kicked' // { channel_id, user_id, kicked_by, reason }
'channel:member_banned' // { channel_id, user_id, banned_by, reason, expires_at }
// Settings & Permissions
'channel:settings_updated' // { channel_id, settings }
'channel:permissions_updated' // { channel_id, overrides }
'channel:synced' // { channel_id, category_id }
'channel:unsynced' // { channel_id }
10.2 Category Events
'category:created' // { category, workspace_id }
'category:updated' // { category_id, updates }
'category:deleted' // { category_id, workspace_id }
'category:reordered' // { workspace_id, category_ids }
'channels:reordered' // { category_id, channel_ids }
10.3 Workspace Events
'workspace:updated' // { workspace_id, updates }
'workspace:deleted' // { workspace_id }
'workspace:role_created' // { workspace_id, role }
'workspace:role_updated' // { workspace_id, role_id, updates }
'workspace:role_deleted' // { workspace_id, role_id }
10.4 Community Events
'community:created' // { community, workspace_id }
'community:updated' // { community_id, updates }
'community:deleted' // { community_id }
'community:group_added' // { community_id, channel_id }
'community:group_removed' // { community_id, channel_id }
10.5 Broadcast Events
'broadcast:sent' // { broadcast_list_id, message_id }
'broadcast:delivered' // { broadcast_list_id, message_id, user_id }
'broadcast:read' // { broadcast_list_id, message_id, user_id }
'broadcast:subscribed' // { broadcast_list_id, user_id }
'broadcast:unsubscribed' // { broadcast_list_id, user_id }
10.6 Event Subscription Patterns
// Client-side subscription
socket.join(`workspace:${workspaceId}`)
socket.join(`channel:${channelId}`)
socket.join(`user:${userId}`) // For DM notifications
// Event handlers
socket.on('channel:created', (data) => {
channelStore.addChannel(data.channel)
})
socket.on('channel:member_joined', (data) => {
if (data.user_id === currentUserId) {
// I joined a new channel
channelStore.addChannel(await fetchChannel(data.channel_id))
} else {
// Someone else joined
channelStore.addChannelMember(data.channel_id, data.member)
}
})
11. UI Components
11.1 Channel List Sidebar
File: /src/components/channel/channel-list.tsx
interface ChannelListProps {
workspaceId: string
activeChannelId?: string
onChannelSelect: (channelId: string) => void
}
// Structure:
// - Starred/Pinned Channels (collapsible)
// - Direct Messages (collapsible)
// - Categories with Channels
// - Category Header (collapsible)
// - Channel Items
// - Uncategorized Channels
Features:
- Drag-and-drop reordering
- Collapse/expand categories
- Unread badges
- Mute indicators
- Context menu on right-click
- Quick actions on hover
11.2 Channel Settings Modal
File: /src/components/channel/channel-settings-modal.tsx
Tabs:
- Overview: Name, topic, description, icon
- Permissions: Role/user permission editor
- Integrations: Webhooks, bots
- Members: Member list with role management
- Invites: Active invites, create new
- Danger Zone: Archive, delete, transfer
11.3 Member List Panel
File: /src/components/channel/channel-members.tsx
interface ChannelMembersProps {
channelId: string
showOnlineStatus?: boolean
groupByRole?: boolean
}
Features:
- Online/offline grouping
- Role-based grouping
- Search/filter members
- Quick actions (DM, kick, ban)
- Member count display
11.4 Permission Editor
File: /src/components/permissions/permission-editor.tsx
interface PermissionEditorProps {
channelId: string
categoryId?: string
onSave: (overrides: PermissionOverride[]) => void
}
Features:
- Visual permission grid
- Role/user toggle views
- Allow/Deny/Inherit states
- Permission inheritance visualization
- Sync with category toggle
11.5 Category Management
File: /src/components/channel/category-manager.tsx
Features:
- Create/edit categories
- Drag-drop reorder
- Set default permissions
- Collapse all / expand all
- Category context menu
11.6 Component Inventory
| Component | Status | Location |
|---|---|---|
| ChannelList | Exists | /src/components/channel/channel-list.tsx |
| ChannelItem | Exists | /src/components/channel/channel-item.tsx |
| ChannelCategory | Exists | /src/components/channel/channel-category.tsx |
| ChannelHeader | Exists | /src/components/channel/channel-header.tsx |
| ChannelMembers | Exists | /src/components/channel/channel-members.tsx |
| ChannelSettingsModal | Exists | /src/components/channel/channel-settings-modal.tsx |
| CreateChannelModal | Exists | /src/components/channel/create-channel-modal.tsx |
| ChannelInviteModal | Exists | /src/components/channel/channel-invite-modal.tsx |
| ChannelInfoPanel | Exists | /src/components/channel/channel-info-panel.tsx |
| PermissionEditor | New | /src/components/permissions/permission-editor.tsx |
| CategoryManager | New | /src/components/channel/category-manager.tsx |
| WorkspaceSettings | New | /src/components/workspace/workspace-settings.tsx |
| CommunityManager | New | /src/components/community/community-manager.tsx |
| BroadcastComposer | New | /src/components/broadcast/broadcast-composer.tsx |
12. Implementation Phases
Phase 1: Foundation (Week 1-2)
Goal: Core channel types and CRUD working with database
| Task | Effort | Owner |
|---|---|---|
| Update database schema | 8h | Backend |
| Extended channel type enum | 2h | Frontend |
| Channel CRUD mutations | 8h | Backend |
| Channel CRUD hooks | 6h | Frontend |
| Basic permission checks | 8h | Backend |
| Update channel store | 4h | Frontend |
Deliverables:
- All channel types can be created/updated/deleted
- Basic membership management works
- Permissions are checked server-side
Phase 2: Categories & Organization (Week 3)
Goal: Full category support with permission inheritance
| Task | Effort | Owner |
|---|---|---|
| Category CRUD | 6h | Backend |
| Category UI components | 8h | Frontend |
| Drag-drop reordering | 8h | Frontend |
| Permission inheritance | 8h | Backend |
| Sync/unsync UI | 4h | Frontend |
Deliverables:
- Categories can be created/edited/deleted
- Channels can be moved between categories
- Permission sync works correctly
Phase 3: Workspaces & Roles (Week 4-5)
Goal: Discord-style server structure
| Task | Effort | Owner |
|---|---|---|
| Workspace model & API | 16h | Backend |
| Role management | 16h | Backend |
| Permission bitfield system | 8h | Backend |
| Role assignment UI | 12h | Frontend |
| Workspace settings UI | 12h | Frontend |
Deliverables:
- Workspaces with full settings
- Role-based permissions working
- Permission editor functional
Phase 4: Communities & Broadcast (Week 6)
Goal: WhatsApp-style communities and broadcast lists
| Task | Effort | Owner |
|---|---|---|
| Community model & API | 12h | Backend |
| Community UI | 12h | Frontend |
| Broadcast list system | 12h | Backend |
| Broadcast UI | 8h | Frontend |
| Delivery tracking | 8h | Backend |
Deliverables:
- Communities with sub-groups
- Broadcast lists functional
- Delivery analytics working
Phase 5: Real-time & Polish (Week 7-8)
Goal: Real-time sync and UI polish
| Task | Effort | Owner |
|---|---|---|
| Socket event handlers | 16h | Full-stack |
| Optimistic updates | 8h | Frontend |
| Error handling | 8h | Full-stack |
| Loading states | 4h | Frontend |
| Accessibility audit | 8h | Frontend |
| Performance optimization | 8h | Full-stack |
Deliverables:
- All events sync in real-time
- Smooth UX with loading states
- WCAG AA compliance
13. Testing Requirements
13.1 Unit Tests
// Channel type tests
describe('ChannelType', () => {
it('validates channel types correctly')
it('enforces type-specific constraints')
it('handles type promotion correctly')
})
// Permission tests
describe('ChannelPermissions', () => {
it('calculates effective permissions correctly')
it('applies overrides in correct order')
it('handles permission inheritance')
it('respects owner/admin bypass')
})
// Store tests
describe('ChannelStore', () => {
it('adds and removes channels')
it('updates channel properties')
it('manages categories correctly')
it('handles optimistic updates')
})
13.2 Integration Tests
// API tests
describe('Channel API', () => {
it('creates channel with correct permissions')
it('enforces membership restrictions')
it('handles concurrent updates')
it('cascades deletions correctly')
})
// Real-time tests
describe('Channel Events', () => {
it('broadcasts channel updates to members')
it('handles reconnection gracefully')
it('syncs state across clients')
})
13.3 E2E Tests
// User flows
describe('Channel Management', () => {
it('creates a new channel and invites members')
it('manages channel permissions')
it('archives and restores channels')
it('handles category organization')
})
describe('Community Features', () => {
it('creates community with announcement channel')
it('adds and removes sub-groups')
it('sends community-wide announcements')
})
13.4 Coverage Requirements
| Area | Target |
|---|---|
| Unit tests | 100% |
| Integration tests | 100% |
| E2E critical paths | 100% |
| Edge cases | 90%+ |
14. Migration Strategy
14.1 Data Migration
For existing nchat installations:
-- Step 1: Add new columns with defaults
ALTER TABLE nchat_channels
ADD COLUMN IF NOT EXISTS workspace_id UUID,
ADD COLUMN IF NOT EXISTS subtype VARCHAR(30),
ADD COLUMN IF NOT EXISTS permission_sync_id UUID;
-- Step 2: Create default workspace
INSERT INTO nchat_workspaces (id, name, slug, owner_id)
SELECT gen_random_uuid(), 'Default Workspace', 'default',
(SELECT id FROM nchat_users WHERE role = 'owner' LIMIT 1);
-- Step 3: Assign channels to default workspace
UPDATE nchat_channels
SET workspace_id = (SELECT id FROM nchat_workspaces WHERE slug = 'default')
WHERE workspace_id IS NULL;
-- Step 4: Migrate permissions to new format
INSERT INTO nchat_channel_permission_overrides (channel_id, target_type, target_id, allow_permissions, deny_permissions)
SELECT
c.id,
'role',
r.id,
-- Convert old permission format to bitfield
CASE WHEN (c.settings->>'allowSendMessages')::boolean THEN 4 ELSE 0 END |
CASE WHEN (c.settings->>'allowReactions')::boolean THEN 64 ELSE 0 END,
0
FROM nchat_channels c
CROSS JOIN nchat_workspace_roles r
WHERE r.is_default = TRUE;
14.2 API Versioning
- New endpoints:
/api/v2/channels - Deprecated endpoints: Mark
/api/channelsas deprecated - Migration period: 6 months
- Breaking changes documented in changelog
14.3 Rollback Plan
# Rollback SQL
BEGIN;
-- Restore old schema
ALTER TABLE nchat_channels DROP COLUMN workspace_id;
-- etc.
COMMIT;
References
Platform Documentation
Internal Documentation
/src/types/channel.ts- Existing channel types/src/lib/rbac/channel-permissions.ts- Permission system/src/stores/channel-store.ts- Zustand store/TODO.md- Tasks 60-65
Appendix A: Permission Bitfield Reference
| Permission | Bit | Value | Description |
|---|---|---|---|
| VIEW_CHANNEL | 0 | 1 | View channel and read messages |
| READ_MESSAGE_HISTORY | 1 | 2 | Read message history |
| SEND_MESSAGES | 2 | 4 | Send messages |
| SEND_MESSAGES_IN_THREADS | 3 | 8 | Send in threads |
| EMBED_LINKS | 4 | 16 | Embed links |
| ATTACH_FILES | 5 | 32 | Attach files |
| ADD_REACTIONS | 6 | 64 | Add reactions |
| USE_EXTERNAL_EMOJIS | 7 | 128 | Use external emojis |
| USE_EXTERNAL_STICKERS | 8 | 256 | Use external stickers |
| MENTION_EVERYONE | 9 | 512 | @everyone and @here |
| MENTION_ROLES | 10 | 1024 | @role mentions |
| CREATE_PUBLIC_THREADS | 11 | 2048 | Create public threads |
| CREATE_PRIVATE_THREADS | 12 | 4096 | Create private threads |
| CONNECT | 13 | 8192 | Connect to voice |
| SPEAK | 14 | 16384 | Speak in voice |
| VIDEO | 15 | 32768 | Use video |
| USE_SOUNDBOARD | 16 | 65536 | Use soundboard |
| USE_VOICE_ACTIVITY | 17 | 131072 | Use voice activity |
| PRIORITY_SPEAKER | 18 | 262144 | Priority speaker |
| MUTE_MEMBERS | 19 | 524288 | Mute others |
| DEAFEN_MEMBERS | 20 | 1048576 | Deafen others |
| MOVE_MEMBERS | 21 | 2097152 | Move others |
| MANAGE_MESSAGES | 22 | 4194304 | Delete others' messages |
| MANAGE_THREADS | 23 | 8388608 | Manage threads |
| MANAGE_CHANNEL | 24 | 16777216 | Edit channel settings |
| SEND_VOICE_MESSAGES | 25 | 33554432 | Send voice messages |
| SEND_POLLS | 26 | 67108864 | Create polls |
| USE_APPLICATION_COMMANDS | 27 | 134217728 | Use slash commands |
Appendix B: Feature Flag Reference
const CHANNEL_FEATURE_FLAGS = {
// Channel types
'channels.public': true,
'channels.private': true,
'channels.dm': true,
'channels.group_dm': true,
'channels.broadcast': true,
'channels.voice': false, // Requires voice infrastructure
'channels.stage': false, // Requires voice infrastructure
'channels.forum': true,
// Features
'channels.categories': true,
'channels.threads': true,
'channels.permissions': true,
'channels.invites': true,
// Structures
'workspaces.enabled': true,
'workspaces.discovery': false, // Requires moderation
'communities.enabled': true,
'broadcast_lists.enabled': true,
// Telegram-style
'channels.supergroups': true,
'channels.gigagroups': false, // Enterprise only
}
Document generated: 2026-02-03 Last updated by: Claude Code