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>
29 KiB
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 manifestpackages/chat-ui/tsconfig.json— TypeScript configpackages/chat-ui/src/index.ts— Barrel export (copied from chatcn, unchanged)packages/chat-ui/src/types.ts— Type definitions (copied from chatcn,ChatThemeremoved)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-uiapps/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
{
"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:
{
"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
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:
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
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
/* @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
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:
- Remove
"use client"directive (line 0) - Remap imports (lines 2-42)
- Replace all
var(--chat-*)with Tailwind classes (throughout) - Remove theme prop from ChatProvider and
data-chat-themeattribute
- 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:
import { cn } from "@/lib/utils"
With:
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 withclassNameusingbg-primary- In
ChatVoiceMessage, the waveform bars use inlinestylewithvar(--chat-accent)— replace withvar(--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/30bg-[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
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:
import { cn } from "@/lib/utils"
With:
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, useborder-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-primaryorbg-primarydepending on context -
var(--chat-red)→text-destructive -
var(--chat-orange)→text-orange-500 -
var(--chat-bg-composer)→bg-card -
Step 4: Commit
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:
import { cn } from "@/lib/utils"
With:
import { cn } from "@xtablo/shared/lib/cn"
Remove ChatTheme from the type imports:
import type { ChatMessageData, ChatUser, TypingUser } from "./types"
The imports from "./chat" stay as relative:
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:
- Each component's Props interface — remove
theme?: ChatTheme - Each component's destructured params — remove
theme = "lunar" - Each
<ChatProvider>usage — removetheme={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
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:
} from "./chat"
With:
} from "./components/chat"
Replace:
} from "./features"
With:
} from "./components/features"
Replace:
} from "./layouts"
With:
} 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:
// Remove this line from the type exports:
// ChatTheme,
- Step 3: Add CSS export
Add at the top of the file:
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
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:
"@xtablo/chat-ui": "workspace:*",
- Step 2: Remove chatscope dependencies
Remove these two lines from dependencies:
"@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
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:
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 —
ChatPageandTabloDiscussionSectiondon'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
onTypingfires(isTyping: boolean)while xtablo'sonTyping/sendTypingis fire-and-forget. We only callonTyping()when typing starts. -
Localization — French strings ("Utilisateur", "Moi", "Envoyer un message...") are kept at the app level as before. The chatcn
formatDateLabelreturns 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):
@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
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:
- Messages render correctly (incoming on left, outgoing on right)
- Avatars show for incoming messages (initials fallback if no avatar URL)
- Message grouping works (consecutive messages from same sender are grouped)
- Date separators appear between days
- Typing indicator shows when someone is typing
- Composer works — type a message and send with Enter
- Auto-scroll to bottom on new messages
- Scroll-to-bottom button appears when scrolled up
- Optimistic messages show with sending status (clock icon)
- 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/60is too faint, trytext-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
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
formatDateLabelusing i18next and pass translated labels, or override the function in the package