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>
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-reactin a single component (apps/main/src/components/ChatMessages.tsx, 184 lines). - The chat data layer (
useChathook 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
ChatThemetype ("lunar" | "aurora" | "ember" | "midnight")data-chat-themeattribute on componentsChatProvider'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)
ChatThemetype removed fromtypes.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
formatDateLabelwith a custom function using i18next - Typing indicator text: pass translated string as a prop
- Placeholder text: pass via
ChatComposerplaceholder prop
Cleanup
After the integration:
- Remove
@chatscope/chat-ui-kit-reactfromapps/main/package.json - Remove
@chatscope/chat-ui-kit-stylesfromapps/main/package.json - Remove the chatscope CSS import from
ChatMessages.tsx - Add
@xtablo/chat-uitoapps/main/package.jsonas a workspace dependency
Out of Scope
- Mobile app:
xtablo-expousesstream-chat-expo— unchanged - Chat data layer:
useChathook, WebSocket connections, chat-worker service — unchanged - ChatChannelPreview and ChatHeader: Already pure Tailwind, no chatscope — unchanged
- TabloDiscussionSection: Consumes
ChatMessageswith 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-chatoverrides inmain.css— unrelated, unchanged