xtablo-source/docs/superpowers/specs/2026-04-12-chatcn-integration-design.md
Arthur Belleville 517526ef99
docs: add chatcn integration design spec and implementation plan
Spec and plan for integrating chatcn as @xtablo/chat-ui package,
replacing chatscope for the chat UI rendering layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:06:52 +02:00

7.4 KiB

Integrate chatcn as @xtablo/chat-ui Package

Overview

Replace the chatscope chat UI library in xtablo-source with chatcn, integrated as a new shared workspace package @xtablo/chat-ui. This replaces the rendering layer only — the chat data layer (useChat hook, WebSocket, REST) is unchanged.

Context

  • chatcn is a React 19 / Tailwind CSS 4 chat component library with messages, composer, reactions, threads, typing indicators, 5 layouts, security utilities, and custom hooks.
  • xtablo-source is a Turborepo monorepo with pnpm workspaces. Chat UI currently uses @chatscope/chat-ui-kit-react in a single component (apps/main/src/components/ChatMessages.tsx, 184 lines).
  • The chat data layer (useChat hook with WebSocket + REST to the chat-worker service) is cleanly separated from the UI and stays unchanged.

Approach

Copy-and-adapt: copy chatcn's chat component source files into a new packages/chat-ui/ workspace package, adapting imports and theming to match xtablo's existing patterns.

Package Structure

packages/chat-ui/
├── package.json              # @xtablo/chat-ui
├── tsconfig.json
└── src/
    ├── index.ts              # Barrel export
    ├── components/
    │   ├── chat.tsx          # Core: ChatProvider, ChatMessage, ChatMessages, ChatComposer, etc.
    │   ├── features.tsx      # ChatForwardDialog, ChatEditComposer, ChatSearch, etc.
    │   └── layouts.tsx       # FullMessenger, ChatWidget, InlineChat, ChatBoard, LiveChat, etc.
    ├── hooks.ts              # groupMessages, useAutoScroll, useAutoResize, useTypingIndicator, etc.
    ├── security.ts           # sanitizeUrl, validateFile, sanitizeFileName, etc.
    └── types.ts              # ChatUser, ChatMessageData, ChatConfig, MessageGroup, etc.

Source-only package (no build step), same pattern as @xtablo/shared and @xtablo/ui.

package.json exports

{
  "name": "@xtablo/chat-ui",
  "version": "0.0.1",
  "private": true,
  "exports": {
    ".": "./src/index.ts",
    "./components/*": "./src/components/*.tsx",
    "./hooks": "./src/hooks.ts",
    "./security": "./src/security.ts",
    "./types": "./src/types.ts"
  },
  "dependencies": {
    "@xtablo/shared": "workspace:*",
    "@xtablo/ui": "workspace:*"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "lucide-react": "*",
    "date-fns": "*"
  }
}

tsconfig.json

Mirrors packages/ui/tsconfig.json: target ES2022, module ESNext, jsx react-jsx, strict true, moduleResolution bundler, declaration true.

Dependencies

All dependencies are already present in the monorepo — no new packages to install:

Dependency Source
@xtablo/shared Workspace (for cn() utility)
@xtablo/ui Workspace (for Button, Dialog, Avatar primitives)
lucide-react Already in monorepo
date-fns Already in monorepo
clsx Via @xtablo/shared
tailwind-merge Via @xtablo/shared

Dropped: @base-ui/react (only used in chatcn's scaffolding button.tsx, not in any chat component).

Import Remapping

When copying chatcn source files into the package, apply these systematic import changes:

chatcn import @xtablo/chat-ui import
import { cn } from "@/lib/utils" import { cn } from "@xtablo/shared/lib/cn"
import { Button } from "@/components/ui/button" import { Button } from "@xtablo/ui/components/button"
Other @/components/ui/* imports (Dialog, Avatar, etc.) Corresponding @xtablo/ui/components/* import

Theming

Strip chatcn's custom theming system entirely and remap to xtablo's existing Tailwind design tokens.

What gets removed

  • ChatTheme type ("lunar" | "aurora" | "ember" | "midnight")
  • data-chat-theme attribute on components
  • ChatProvider's theme prop
  • All var(--chat-*) CSS custom property references
  • chatcn's globals.css theme definitions (not copied)

Token mapping

All var(--chat-*) references in component files are replaced with Tailwind utility classes:

chatcn token Tailwind class
--chat-bg-app, --chat-bg-main bg-background
--chat-bg-sidebar bg-sidebar
--chat-bubble-outgoing bg-primary
--chat-bubble-incoming bg-muted
--chat-text-outgoing text-primary-foreground
--chat-text-incoming text-foreground
--chat-accent bg-accent / text-accent-foreground
--chat-border border-border
--chat-text-primary text-foreground
--chat-text-secondary text-muted-foreground

This ensures automatic light/dark mode support via xtablo's existing .dark class.

Component Adaptation

What stays unchanged

  • All chatcn types (ChatUser, ChatMessageData, ChatConfig, MessageGroup, MessageListItem, TypingUser)
  • All hooks (groupMessages, useAutoScroll, useAutoResize, useTypingIndicator, formatTimestamp, formatDateLabel)
  • All security utilities (sanitizeUrl, validateFile, sanitizeFileName, etc.)
  • All component props and APIs

What changes

  • Imports remapped (see above)
  • Theme references replaced with Tailwind classes (see above)
  • ChatTheme type removed from types.ts
  • Theme prop removed from ChatProvider

Replacing ChatMessages.tsx

The current apps/main/src/components/ChatMessages.tsx is rewritten to use @xtablo/chat-ui.

Data transformation

The app-level component transforms xtablo's data model to chatcn's types:

Current (xtablo) chatcn equivalent
ChatMessage.userId + Member.name ChatMessageData.senderId + senderName
ChatMessage.text ChatMessageData.text
ChatMessage.createdAt (ISO string) ChatMessageData.timestamp (Date)
ChatMessage.optimistic ChatMessageData.status: "sending"
Member.avatar_url ChatUser.avatar / ChatMessageData.senderAvatar
typingUsers: string[] TypingUser[] (with id + name resolved from members)

New component structure

<ChatProvider config={{ currentUser, ... }}>
  <ChatMessages items={messageListItems} onLoadMore={onLoadMore} />
  <ChatComposer onSend={onSend} onChange={onTyping} placeholder={t("chat.placeholder")} />
</ChatProvider>

Localization

The package stays i18n-agnostic. French strings currently hardcoded in ChatMessages.tsx ("Aujourd'hui", "Hier", "écrit...") are handled at the app level:

  • Date labels: override chatcn's formatDateLabel with a custom function using i18next
  • Typing indicator text: pass translated string as a prop
  • Placeholder text: pass via ChatComposer placeholder prop

Cleanup

After the integration:

  • Remove @chatscope/chat-ui-kit-react from apps/main/package.json
  • Remove @chatscope/chat-ui-kit-styles from apps/main/package.json
  • Remove the chatscope CSS import from ChatMessages.tsx
  • Add @xtablo/chat-ui to apps/main/package.json as a workspace dependency

Out of Scope

  • Mobile app: xtablo-expo uses stream-chat-expo — unchanged
  • Chat data layer: useChat hook, WebSocket connections, chat-worker service — unchanged
  • ChatChannelPreview and ChatHeader: Already pure Tailwind, no chatscope — unchanged
  • TabloDiscussionSection: Consumes ChatMessages with the same props interface — unchanged
  • New feature adoption: Reactions, threads, search, pinned messages, etc. are available in the package but not wired up. Future work.
  • Stream Chat CSS: .str-chat overrides in main.css — unrelated, unchanged