Files
2026-05-27 23:29:53 +01:00

4.2 KiB

OpenSpec Change: enterprise-gaming-chat

  • Change ID: enterprise-gaming-chat
  • Package: @suaveplan/chat
  • Category: gaming/multiplayer
  • Target path: gaming/multiplayer/chat
  • Status: PROPOSED
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/chat.md

Summary

Deliver @suaveplan/chat on the genesis branch from scratch at enterprise quality. The package provides in-game chat with named channels, whispers, configurable rate limiting, and a profanity filter. A ChatManager maintains typed ChatChannel objects (global, local, party, trade, custom); messages are validated against rate limit before dispatch; a ProfanityFilter uses a blocklist with configurable mask character; a WhisperRouter routes directed messages between two players. Genesis adds Zod message schemas, structured errors, OTel message-rate counter, injectable clock, and 100% coverage.


Current-state audit (genesis)

Package does not exist on genesis yet — fresh build.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) ChatError; CHAT.* codes
@suaveplan/types (workspace:^) Clock
@suaveplan/events (workspace:^) chat:message, chat:whisper, chat:muted events
zod (catalog:forms) ChatMessage, ChannelConfig schemas
@opentelemetry/api (catalog:observability) Messages-per-second counter

Public API surface

type ChannelType = 'global' | 'local' | 'party' | 'trade' | 'system' | 'custom'

interface ChannelConfig {
  id: string
  type: ChannelType
  displayName: string
  maxHistory?: number      // message ring buffer size, default 100
  rateLimitPerMin?: number // default 30
}

interface ChatMessage {
  id: string
  channelId: string
  senderId: string
  senderName: string
  content: string
  timestamp: number
  filtered?: boolean
}

function createChatManager(options?: ChatManagerOptions): ChatManager

interface ChatManagerOptions {
  profanityFilter?: ProfanityFilter
  clock?: Clock
}

interface ChatManager {
  createChannel(config: ChannelConfig): ChatChannel
  getChannel(id: string): ChatChannel | undefined
  deleteChannel(id: string): void
  sendMessage(channelId: string, senderId: string, senderName: string, content: string): ChatMessage
  whisper(fromId: string, fromName: string, toId: string, content: string): ChatMessage
  mute(playerId: string, durationMs?: number): void
  unmute(playerId: string): void
  isMuted(playerId: string): boolean
  tick(dt: number): void   // expire mutes
}

interface ChatChannel {
  readonly id: string
  readonly config: ChannelConfig
  getHistory(): ChatMessage[]
  subscribe(playerId: string): void
  unsubscribe(playerId: string): void
  readonly subscribers: ReadonlySet<string>
}

// Profanity filter
function createProfanityFilter(blocklist: string[], options?: FilterOptions): ProfanityFilter

interface FilterOptions {
  maskChar?: string    // default '*'
  mode?: 'mask' | 'block'   // block throws, mask replaces
}

interface ProfanityFilter {
  filter(text: string): { filtered: string; triggered: boolean }
  addWord(word: string): void
  removeWord(word: string): void
}

Error codes

type ChatErrorCode =
  | 'CHAT_CHANNEL_NOT_FOUND'
  | 'CHAT_RATE_LIMIT_EXCEEDED'
  | 'CHAT_SENDER_MUTED'
  | 'CHAT_CONTENT_BLOCKED'
  | 'CHAT_RECIPIENT_NOT_FOUND'

File layout

gaming/multiplayer/chat/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/index.ts
    types/common.ts
    types/common.md
    core/
      chat-manager/
        chat-manager.ts
        chat-manager.md
        chat-manager.types.ts
        chat-manager.test.ts
        index.ts
      chat-channel/
        chat-channel.ts
        chat-channel.md
        chat-channel.types.ts
        chat-channel.test.ts
        index.ts
    utils/
      profanity-filter.ts
      profanity-filter.md
      profanity-filter.test.ts
      rate-limiter.ts
      rate-limiter.md
      rate-limiter.test.ts
  tests/
    integration.test.ts

Impact

Downstream: @suaveplan/react-social-game renders chat panel. Prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events.