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>
This commit is contained in:
Arthur Belleville 2026-04-12 12:06:52 +02:00
parent fe001b7fc2
commit 517526ef99
No known key found for this signature in database
2 changed files with 1133 additions and 0 deletions

View file

@ -0,0 +1,946 @@
# chatcn Integration as @xtablo/chat-ui Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace chatscope with chatcn as a new `@xtablo/chat-ui` workspace package, adapting theming to xtablo's design tokens.
**Architecture:** Copy chatcn's 7 source files into `packages/chat-ui/src/`, remap imports to use `@xtablo/shared` and `@xtablo/ui`, replace all `var(--chat-*)` CSS variables with Tailwind utility classes mapped to xtablo's existing design tokens. Then rewrite `ChatMessages.tsx` in the main app to consume the new package.
**Tech Stack:** React 19, Tailwind CSS 4, TypeScript 5, Turborepo/pnpm workspaces
---
## File Map
**New files (packages/chat-ui/):**
- `packages/chat-ui/package.json` — Package manifest
- `packages/chat-ui/tsconfig.json` — TypeScript config
- `packages/chat-ui/src/index.ts` — Barrel export (copied from chatcn, unchanged)
- `packages/chat-ui/src/types.ts` — Type definitions (copied from chatcn, `ChatTheme` removed)
- `packages/chat-ui/src/hooks.ts` — Hooks (copied from chatcn, import remapped)
- `packages/chat-ui/src/security.ts` — Security utils (copied from chatcn, unchanged)
- `packages/chat-ui/src/components/chat.tsx` — Core components (copied, imports + theme adapted)
- `packages/chat-ui/src/components/features.tsx` — Feature components (copied, imports + theme adapted)
- `packages/chat-ui/src/components/layouts.tsx` — Layout components (copied, imports + theme adapted)
- `packages/chat-ui/src/chat-ui.css` — Minimal CSS for animations and utility classes
**Modified files:**
- `apps/main/package.json` — Remove chatscope deps, add `@xtablo/chat-ui`
- `apps/main/src/components/ChatMessages.tsx` — Rewrite to use `@xtablo/chat-ui`
---
## Complete Token Mapping Reference
Every `var(--chat-*)` occurrence in chatcn is replaced with a Tailwind class. This table is the single source of truth for all theme adaptation across Tasks 4-6.
| chatcn CSS variable | Tailwind class (as bg-) | Tailwind class (as text-) | Tailwind class (as border-) | Notes |
|---|---|---|---|---|
| `--chat-bg-app` | `bg-background` | — | — | |
| `--chat-bg-main` | `bg-background` | — | — | |
| `--chat-bg-sidebar` | `bg-card` | — | — | Using `card` for secondary surfaces |
| `--chat-bg-header` | `bg-card` | — | — | With `backdrop-blur` kept |
| `--chat-bg-composer` | `bg-card` | — | — | With `backdrop-blur` kept |
| `--chat-bg-code` | `bg-muted` | — | — | Code block backgrounds |
| `--chat-bg-hover` | `bg-accent` | — | — | Hover states |
| `--chat-bg-content-card` | `bg-card` | — | — | Embedded content cards |
| `--chat-bubble-outgoing` | `bg-primary` | — | — | |
| `--chat-bubble-outgoing-text` | — | `text-primary-foreground` | — | |
| `--chat-bubble-incoming` | `bg-muted` | — | — | |
| `--chat-bubble-incoming-text` | — | `text-foreground` | — | |
| `--chat-accent` | `bg-primary` | `text-primary` | — | chatcn accent = xtablo primary |
| `--chat-accent-soft` | `bg-accent` | — | — | xtablo `accent` is the soft/subtle bg |
| `--chat-text-primary` | — | `text-foreground` | — | |
| `--chat-text-secondary` | — | `text-muted-foreground` | — | |
| `--chat-text-tertiary` | — | `text-muted-foreground/60` | — | Slightly more faded |
| `--chat-border` | — | — | `border-border` | |
| `--chat-border-strong` | — | — | `border-border` | Same token, stronger is unnecessary |
| `--chat-red` | — | `text-destructive` | — | |
| `--chat-orange` | — | `text-orange-500` | — | No xtablo token; use Tailwind orange |
| `--chat-green` | — | `text-green-500` | — | Presence indicator; use Tailwind green |
| `--chat-font-mono` | `font-mono` | — | — | Tailwind built-in |
| `--chat-shadow-lg` | `shadow-lg` | — | — | Tailwind built-in |
| `--chat-shadow-md` | `shadow-md` | — | — | Tailwind built-in |
| `--chat-shadow-toolbar` | `shadow-md` | — | — | |
---
### Task 1: Create package scaffold
**Files:**
- Create: `packages/chat-ui/package.json`
- Create: `packages/chat-ui/tsconfig.json`
- [ ] **Step 1: Create package.json**
```json
{
"name": "@xtablo/chat-ui",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./components/*": "./src/components/*.tsx",
"./hooks": "./src/hooks.ts",
"./security": "./src/security.ts",
"./types": "./src/types.ts"
},
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format --write ."
},
"dependencies": {
"@xtablo/shared": "workspace:*",
"@xtablo/ui": "workspace:*"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"lucide-react": "*",
"date-fns": "*"
},
"devDependencies": {
"@biomejs/biome": "2.2.5",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"typescript": "^5.7.0"
}
}
```
- [ ] **Step 2: Create tsconfig.json**
Copy verbatim from `packages/ui/tsconfig.json`:
```json
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "bundler",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022",
"jsx": "react-jsx"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
```
- [ ] **Step 3: Run pnpm install to register the new workspace package**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm install`
Expected: lockfile updated, `@xtablo/chat-ui` recognized as workspace package
- [ ] **Step 4: Commit**
```bash
git add packages/chat-ui/package.json packages/chat-ui/tsconfig.json pnpm-lock.yaml
git commit -m "feat(chat-ui): scaffold @xtablo/chat-ui package"
```
---
### Task 2: Copy non-component source files (types, hooks, security)
**Files:**
- Create: `packages/chat-ui/src/types.ts`
- Create: `packages/chat-ui/src/hooks.ts`
- Create: `packages/chat-ui/src/security.ts`
- Source: `chatcn/src/components/ui/chat/types.ts`, `hooks.ts`, `security.ts`
- [ ] **Step 1: Copy types.ts from chatcn and remove ChatTheme**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/types.ts` to `packages/chat-ui/src/types.ts`.
Remove the last line:
```typescript
export type ChatTheme = "lunar" | "aurora" | "ember" | "midnight"
```
No other changes needed — this file has no imports.
- [ ] **Step 2: Copy security.ts from chatcn**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/security.ts` to `packages/chat-ui/src/security.ts`.
No changes needed — this file has no imports from chatcn internals.
- [ ] **Step 3: Copy hooks.ts from chatcn and fix the "use client" directive**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/hooks.ts` to `packages/chat-ui/src/hooks.ts`.
Remove the `"use client"` directive at the top (not needed in a Vite app — it's a Next.js RSC directive).
The import `from "./types"` stays — it's a relative import within the same package.
- [ ] **Step 4: Verify types compile**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm --filter @xtablo/chat-ui typecheck`
Expected: no type errors
- [ ] **Step 5: Commit**
```bash
git add packages/chat-ui/src/types.ts packages/chat-ui/src/hooks.ts packages/chat-ui/src/security.ts
git commit -m "feat(chat-ui): add types, hooks, and security utilities from chatcn"
```
---
### Task 3: Create chat-ui.css with animations and utility classes
**Files:**
- Create: `packages/chat-ui/src/chat-ui.css`
chatcn uses several CSS classes for animations and utility styles that aren't Tailwind classes. These need a small CSS file.
- [ ] **Step 1: Create chat-ui.css**
```css
/* @xtablo/chat-ui — Animations and utility classes */
/* ─── Message entry ─────────────────────────────────────────────── */
@keyframes chat-message-enter {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ─── Toolbar entrance ──────────────────────────────────────────── */
@keyframes chat-toolbar-enter {
from { opacity: 0; transform: scale(0.95) translateY(4px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
/* ─── Reaction pop ──────────────────────────────────────────────── */
@keyframes chat-reaction-pop {
0% { transform: scale(0); opacity: 0; }
70% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
/* ─── Typing indicator dots ─────────────────────────────────────── */
@keyframes chat-typing-pulse {
0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
30% { opacity: 1; transform: translateY(-4px); }
}
/* ─── Cursor blink (streaming) ──────────────────────────────────── */
@keyframes chat-cursor-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* ─── Read receipt status color transition ───────────────────────── */
@keyframes chat-status-read-in {
from { color: var(--color-muted-foreground); }
to { color: var(--color-primary); }
}
/* ─── Utility classes ───────────────────────────────────────────── */
@layer base {
.chat-message {
animation: chat-message-enter 250ms cubic-bezier(0.25, 0.1, 0.25, 1.0);
}
.chat-typing-dot {
animation: chat-typing-pulse 1.4s ease-in-out infinite;
}
.chat-toolbar-enter {
transform-origin: bottom center;
}
.group\/message:hover .chat-toolbar-enter {
animation: chat-toolbar-enter 150ms ease-out;
}
.chat-reaction-pop {
animation: chat-reaction-pop 200ms cubic-bezier(0.25, 0.1, 0.25, 1.0);
}
.chat-status-read {
animation: chat-status-read-in 400ms ease-out;
}
.chat-streaming-cursor {
animation: chat-cursor-blink 1s step-end infinite;
}
.chat-content-card {
background: var(--color-card);
border: 1px solid var(--color-border);
border-radius: 12px;
overflow: hidden;
}
.chat-drop-overlay {
position: absolute;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in oklch, var(--color-background) 80%, transparent);
border: 2px dashed var(--color-primary);
border-radius: 12px;
backdrop-filter: blur(4px);
}
}
/* ─── Reduced motion ────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.chat-message,
.chat-typing-dot,
.chat-toolbar-enter,
.chat-reaction-pop,
.chat-status-read {
animation: none;
}
}
```
Note: This CSS uses `var(--color-*)` (Tailwind's theme inline variables, e.g., `--color-primary` maps to xtablo's `--primary`). This is how you reference design tokens from CSS in Tailwind v4.
- [ ] **Step 2: Commit**
```bash
git add packages/chat-ui/src/chat-ui.css
git commit -m "feat(chat-ui): add animation and utility CSS"
```
---
### Task 4: Copy and adapt chat.tsx (core components)
**Files:**
- Create: `packages/chat-ui/src/components/chat.tsx`
- Source: `chatcn/src/components/ui/chat/chat.tsx` (1415 lines)
This is the largest file. Apply three systematic transformations:
1. **Remove `"use client"` directive** (line 0)
2. **Remap imports** (lines 2-42)
3. **Replace all `var(--chat-*)` with Tailwind classes** (throughout)
4. **Remove theme prop from ChatProvider** and `data-chat-theme` attribute
- [ ] **Step 1: Copy the file**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/chat.tsx` to `packages/chat-ui/src/components/chat.tsx`.
- [ ] **Step 2: Remove "use client" directive**
Delete the first line `"use client"`.
- [ ] **Step 3: Remap imports**
Replace:
```typescript
import { cn } from "@/lib/utils"
```
With:
```typescript
import { cn } from "@xtablo/shared/lib/cn"
```
The `import type { ..., ChatTheme } from "./types"` — remove `ChatTheme` from this import.
All other imports (React, lucide-react, createPortal, local ./types, ./hooks) stay as-is since they're either external packages or relative imports within the package.
- [ ] **Step 4: Remove theme from ChatProvider**
In `ChatProviderProps` interface (~line 57-71): remove `theme?: ChatTheme` prop.
In `ChatProvider` function (~line 73-110):
- Remove `theme = "lunar"` from destructured params
- Remove `data-chat-theme={theme}` from the wrapping div — change to just `<div style={style} className={className}>`
- [ ] **Step 5: Replace all var(--chat-*) occurrences with Tailwind classes**
This is the bulk of the work. Apply the token mapping table from above systematically throughout the file. The pattern is to replace inline `var(--chat-*)` within className strings.
Examples of transformations:
```
// BEFORE
bg-[var(--chat-bg-sidebar)]
// AFTER
bg-card
// BEFORE
text-[var(--chat-text-primary)]
// AFTER
text-foreground
// BEFORE
text-[var(--chat-text-secondary)]
// AFTER
text-muted-foreground
// BEFORE
text-[var(--chat-text-tertiary)]
// AFTER
text-muted-foreground/60
// BEFORE
border-[var(--chat-border)]
// AFTER
border-border
// BEFORE
border-[var(--chat-border-strong)]
// AFTER
border-border
// BEFORE
bg-[var(--chat-accent-soft)]
// AFTER
bg-accent
// BEFORE
text-[var(--chat-accent)]
// AFTER
text-primary
// BEFORE
bg-[var(--chat-accent)]
// AFTER
bg-primary
// BEFORE
bg-[var(--chat-bubble-outgoing)]
// AFTER
bg-primary
// BEFORE
text-[var(--chat-bubble-outgoing-text)]
// AFTER
text-primary-foreground
// BEFORE
bg-[var(--chat-bubble-incoming)]
// AFTER
bg-muted
// BEFORE
text-[var(--chat-bubble-incoming-text)]
// AFTER
text-foreground
// BEFORE
bg-[var(--chat-bg-main)]
// AFTER
bg-background
// BEFORE
bg-[var(--chat-bg-composer)]
// AFTER
bg-card
// BEFORE
shadow-[var(--chat-shadow-toolbar)]
// AFTER
shadow-md
// BEFORE
shadow-[var(--chat-shadow-lg)]
// AFTER
shadow-lg
// BEFORE
shadow-[var(--chat-shadow-md)]
// AFTER
shadow-md
// BEFORE
text-[var(--chat-red)]
// AFTER
text-destructive
// BEFORE
bg-[var(--chat-bg-code)]
// AFTER
bg-muted
// BEFORE
style={{ fontFamily: "var(--chat-font-mono)" }}
// AFTER (use className instead)
className="font-mono ..."
```
For inline `style` attributes that reference `var(--chat-*)`:
- `style={{ background: "var(--chat-accent)" }}` → replace with `className` using `bg-primary`
- In `ChatVoiceMessage`, the waveform bars use inline `style` with `var(--chat-accent)` — replace with `var(--color-primary)` (Tailwind v4 resolved token)
- In `ChatVoiceMessage`, `style={{ color: "white" }}` stays (it's already a concrete value)
Special cases in `ChatMessageReply`:
- `border-[var(--chat-bubble-outgoing-text)]/30``border-primary-foreground/30`
- `bg-[var(--chat-bubble-outgoing-text)]/10``bg-primary-foreground/10`
Special cases in `ChatMessageReactions`:
- `border-[var(--chat-accent)]/30``border-primary/30`
- [ ] **Step 6: Verify types compile**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm --filter @xtablo/chat-ui typecheck`
Expected: no type errors (or only errors from missing features.tsx/layouts.tsx imports, which is fine for now)
- [ ] **Step 7: Commit**
```bash
git add packages/chat-ui/src/components/chat.tsx
git commit -m "feat(chat-ui): add core chat components with xtablo theming"
```
---
### Task 5: Copy and adapt features.tsx
**Files:**
- Create: `packages/chat-ui/src/components/features.tsx`
- Source: `chatcn/src/components/ui/chat/features.tsx` (510 lines)
- [ ] **Step 1: Copy the file**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/features.tsx` to `packages/chat-ui/src/components/features.tsx`.
- [ ] **Step 2: Remove "use client" and remap imports**
Remove `"use client"` directive.
Replace:
```typescript
import { cn } from "@/lib/utils"
```
With:
```typescript
import { cn } from "@xtablo/shared/lib/cn"
```
Other imports (`lucide-react`, `./types`, `./hooks`) stay as-is.
- [ ] **Step 3: Replace all var(--chat-*) with Tailwind classes**
Apply the same token mapping as Task 4. This file uses these tokens:
- `var(--chat-border-strong)` → remove bracket, use `border-border`
- `var(--chat-bg-sidebar)``bg-card`
- `var(--chat-shadow-lg)``shadow-lg`
- `var(--chat-shadow-toolbar)``shadow-md`
- `var(--chat-border)``border-border`
- `var(--chat-text-primary)``text-foreground`
- `var(--chat-text-secondary)``text-muted-foreground`
- `var(--chat-text-tertiary)``text-muted-foreground/60`
- `var(--chat-bg-main)``bg-background`
- `var(--chat-accent-soft)``bg-accent`
- `var(--chat-bubble-incoming)``bg-muted`
- `var(--chat-accent)``text-primary` or `bg-primary` depending on context
- `var(--chat-red)``text-destructive`
- `var(--chat-orange)``text-orange-500`
- `var(--chat-bg-composer)``bg-card`
- [ ] **Step 4: Commit**
```bash
git add packages/chat-ui/src/components/features.tsx
git commit -m "feat(chat-ui): add feature components with xtablo theming"
```
---
### Task 6: Copy and adapt layouts.tsx
**Files:**
- Create: `packages/chat-ui/src/components/layouts.tsx`
- Source: `chatcn/src/components/ui/chat/layouts.tsx` (822 lines)
- [ ] **Step 1: Copy the file**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/layouts.tsx` to `packages/chat-ui/src/components/layouts.tsx`.
- [ ] **Step 2: Remove "use client" and remap imports**
Remove `"use client"` directive.
Replace:
```typescript
import { cn } from "@/lib/utils"
```
With:
```typescript
import { cn } from "@xtablo/shared/lib/cn"
```
Remove `ChatTheme` from the type imports:
```typescript
import type { ChatMessageData, ChatUser, TypingUser } from "./types"
```
The imports from `"./chat"` stay as relative:
```typescript
import { ChatProvider, ChatMessages, ChatComposer } from "./chat"
```
- [ ] **Step 3: Remove theme prop from all layout components**
Every layout component (`FullMessenger`, `ChatWidget`, `InlineChat`, `ChatBoard`, `LiveChat`) has a `theme?: ChatTheme` prop passed to `<ChatProvider>`. Remove it from:
1. Each component's Props interface — remove `theme?: ChatTheme`
2. Each component's destructured params — remove `theme = "lunar"`
3. Each `<ChatProvider>` usage — remove `theme={theme}`
- [ ] **Step 4: Replace all var(--chat-*) with Tailwind classes**
Same mapping as previous tasks. This file uses the same set of tokens plus:
- `var(--chat-bg-app)``bg-background`
- `var(--chat-bg-header)``bg-card`
- `var(--chat-green)``text-green-500` / `bg-green-500`
- [ ] **Step 5: Commit**
```bash
git add packages/chat-ui/src/components/layouts.tsx
git commit -m "feat(chat-ui): add layout components with xtablo theming"
```
---
### Task 7: Create barrel export (index.ts)
**Files:**
- Create: `packages/chat-ui/src/index.ts`
- Source: `chatcn/src/components/ui/chat/index.ts`
- [ ] **Step 1: Copy index.ts from chatcn**
Copy `/Users/arthur.belleville/Documents/perso/projects/chatcn/src/components/ui/chat/index.ts` to `packages/chat-ui/src/index.ts`.
- [ ] **Step 2: Update import paths**
The chatcn index.ts uses `"./chat"`, `"./features"`, `"./layouts"`, `"./security"`, `"./types"`, `"./hooks"`. Update the component imports to point into the `components/` subdirectory:
Replace:
```typescript
} from "./chat"
```
With:
```typescript
} from "./components/chat"
```
Replace:
```typescript
} from "./features"
```
With:
```typescript
} from "./components/features"
```
Replace:
```typescript
} from "./layouts"
```
With:
```typescript
} from "./components/layouts"
```
The `"./security"`, `"./types"`, and `"./hooks"` imports stay as-is (they're at the src root).
Also remove `ChatTheme` from the types export section:
```typescript
// Remove this line from the type exports:
// ChatTheme,
```
- [ ] **Step 3: Add CSS export**
Add at the top of the file:
```typescript
import "./chat-ui.css"
```
This ensures the CSS is included when the package is consumed.
- [ ] **Step 4: Verify full package compiles**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm --filter @xtablo/chat-ui typecheck`
Expected: PASS with no errors
- [ ] **Step 5: Commit**
```bash
git add packages/chat-ui/src/index.ts
git commit -m "feat(chat-ui): add barrel export and wire up CSS"
```
---
### Task 8: Update main app dependencies
**Files:**
- Modify: `apps/main/package.json`
- [ ] **Step 1: Add @xtablo/chat-ui dependency**
In `apps/main/package.json`, add to the `dependencies` section:
```json
"@xtablo/chat-ui": "workspace:*",
```
- [ ] **Step 2: Remove chatscope dependencies**
Remove these two lines from `dependencies`:
```json
"@chatscope/chat-ui-kit-react": "^2.1.1",
"@chatscope/chat-ui-kit-styles": "^1.4.0",
```
- [ ] **Step 3: Run pnpm install**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm install`
Expected: lockfile updated, chatscope removed, @xtablo/chat-ui linked
- [ ] **Step 4: Commit**
```bash
git add apps/main/package.json pnpm-lock.yaml
git commit -m "feat(main): switch from chatscope to @xtablo/chat-ui"
```
---
### Task 9: Rewrite ChatMessages.tsx to use @xtablo/chat-ui
**Files:**
- Modify: `apps/main/src/components/ChatMessages.tsx`
This is the core integration point. The current file is 184 lines using chatscope components. Replace it entirely with a new implementation using `@xtablo/chat-ui`.
- [ ] **Step 1: Rewrite ChatMessages.tsx**
Replace the entire contents of `apps/main/src/components/ChatMessages.tsx` with:
```tsx
import { useMemo } from "react";
import {
ChatProvider,
ChatMessages as ChatMessageList,
ChatComposer,
ChatTypingIndicator,
} from "@xtablo/chat-ui";
import type { ChatMessageData, ChatUser, TypingUser } from "@xtablo/chat-ui";
interface ChatMessage {
id: string;
userId: string;
text: string;
createdAt: string;
clientId: string;
optimistic?: boolean;
}
interface Member {
id: string;
name: string;
avatar_url: string | null;
}
interface ChatMessagesProps {
messages: ChatMessage[];
currentUserId: string;
members: Member[];
typingUsers: string[];
hasMoreMessages: boolean;
onLoadMore?: () => void;
onSend: (text: string) => void;
onTyping: () => void;
placeholder?: string;
}
export function ChatMessages({
messages,
currentUserId,
members,
typingUsers,
hasMoreMessages,
onLoadMore,
onSend,
onTyping,
placeholder = "Envoyer un message...",
}: ChatMessagesProps) {
const membersById = useMemo(() => {
const map = new Map<string, Member>();
for (const m of members) {
map.set(m.id, m);
}
return map;
}, [members]);
const currentUser = useMemo<ChatUser>(
() => ({
id: currentUserId,
name: membersById.get(currentUserId)?.name ?? "Moi",
avatar: membersById.get(currentUserId)?.avatar_url ?? undefined,
}),
[currentUserId, membersById],
);
const chatMessages = useMemo<ChatMessageData[]>(
() =>
messages.map((msg) => {
const member = membersById.get(msg.userId);
return {
id: msg.id,
senderId: msg.userId,
senderName: member?.name ?? "Utilisateur",
senderAvatar: member?.avatar_url ?? undefined,
text: msg.text,
timestamp: new Date(msg.createdAt),
status: msg.optimistic ? "sending" : undefined,
};
}),
[messages, membersById],
);
const chatTypingUsers = useMemo<TypingUser[]>(
() =>
typingUsers.map((userId) => ({
id: userId,
name: membersById.get(userId)?.name ?? "Utilisateur",
avatar: membersById.get(userId)?.avatar_url ?? undefined,
})),
[typingUsers, membersById],
);
return (
<ChatProvider
currentUser={currentUser}
className="flex h-full flex-col"
>
<ChatMessageList
messages={chatMessages}
typingUsers={chatTypingUsers}
/>
<ChatComposer
onSend={onSend}
onTyping={(_isTyping) => {
if (_isTyping) onTyping();
}}
placeholder={placeholder}
/>
</ChatProvider>
);
}
```
Key decisions:
- **Props interface stays identical**`ChatPage` and `TabloDiscussionSection` don't need changes.
- **Data transformation** happens in `useMemo` — maps xtablo's flat message/member model to chatcn's richer types.
- **Typing callback bridge** — chatcn's `onTyping` fires `(isTyping: boolean)` while xtablo's `onTyping` / `sendTyping` is fire-and-forget. We only call `onTyping()` when typing starts.
- **Localization** — French strings ("Utilisateur", "Moi", "Envoyer un message...") are kept at the app level as before. The chatcn `formatDateLabel` returns English labels ("Today", "Yesterday") — this is acceptable for now and can be overridden later with i18next.
- [ ] **Step 2: Import the CSS in the main app**
In `apps/main/src/main.css`, add the chat-ui CSS import at the top (after tailwindcss):
```css
@import "tailwindcss";
@import "tw-animate-css";
@import "@xtablo/chat-ui/src/chat-ui.css";
```
Alternatively, if the `import "./chat-ui.css"` in `index.ts` works via Vite's CSS handling (it should, since Vite processes CSS imports in JS), this explicit import may not be needed. Test by checking if animations work in the browser. If they do, skip this step.
- [ ] **Step 3: Verify types compile**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm --filter @xtablo/main typecheck`
Expected: PASS with no errors
- [ ] **Step 4: Commit**
```bash
git add apps/main/src/components/ChatMessages.tsx
git commit -m "feat(main): rewrite ChatMessages to use @xtablo/chat-ui"
```
---
### Task 10: Visual testing and cleanup
**Files:**
- Possibly modify: `apps/main/src/main.css` (CSS import if needed)
- Possibly modify: `packages/chat-ui/src/components/chat.tsx` (style fixes)
- [ ] **Step 1: Start the dev server**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm dev:main`
Expected: Main app starts on port 5173
- [ ] **Step 2: Test the chat page**
Open http://localhost:5173 in a browser. Navigate to a chat channel. Verify:
1. Messages render correctly (incoming on left, outgoing on right)
2. Avatars show for incoming messages (initials fallback if no avatar URL)
3. Message grouping works (consecutive messages from same sender are grouped)
4. Date separators appear between days
5. Typing indicator shows when someone is typing
6. Composer works — type a message and send with Enter
7. Auto-scroll to bottom on new messages
8. Scroll-to-bottom button appears when scrolled up
9. Optimistic messages show with sending status (clock icon)
10. Dark mode looks correct (toggle dark mode if available)
- [ ] **Step 3: Test the tablo discussion section**
Navigate to a tablo detail page that has a discussion section. Verify the chat works there too — same component, same behavior.
- [ ] **Step 4: Fix any visual issues**
If any theme tokens don't look right (e.g., contrast issues, colors too similar), adjust the mapping in the affected component file. Common issues:
- If `text-muted-foreground/60` is too faint, try `text-muted-foreground/70`
- If outgoing bubble color (bg-primary) doesn't feel "chat-like", consider adding a chat-specific CSS variable in `main.css`
- [ ] **Step 5: Remove chatscope CSS import if still present anywhere**
Search for any remaining chatscope references:
Run: `grep -r "chatscope" apps/main/src/`
Expected: no results
- [ ] **Step 6: Final typecheck and lint**
Run: `cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && pnpm typecheck && pnpm lint`
Expected: PASS
- [ ] **Step 7: Commit any fixes**
```bash
git add -A
git commit -m "fix(chat-ui): visual polish and cleanup after integration"
```
---
## Post-Integration Notes
After all tasks are complete:
- **chatscope** is fully removed — no dependencies, no CSS import, no component usage
- **@xtablo/chat-ui** is a self-contained workspace package with all chatcn components
- **ChatMessages.tsx** keeps its original props interface — no changes needed in consuming components
- **Future features** (reactions, threads, search, pinned messages) are available in the package and can be wired up incrementally
- **Localization** — date labels are in English. To switch to French, create a custom `formatDateLabel` using i18next and pass translated labels, or override the function in the package

View file

@ -0,0 +1,187 @@
# 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
```json
{
"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
```tsx
<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