2026-01-13 18:47:57 +00:00
|
|
|
import React, {
|
|
|
|
|
useState,
|
|
|
|
|
useCallback,
|
|
|
|
|
useRef,
|
|
|
|
|
useEffect,
|
|
|
|
|
lazy,
|
|
|
|
|
Suspense,
|
|
|
|
|
} from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Send,
|
|
|
|
|
Smile,
|
|
|
|
|
Paperclip,
|
|
|
|
|
X,
|
|
|
|
|
Image as ImageIcon,
|
|
|
|
|
File,
|
|
|
|
|
Mic,
|
|
|
|
|
} from 'lucide-react';
|
2025-12-03 21:56:50 +00:00
|
|
|
import { useChat } from '../hooks/useChat';
|
|
|
|
|
import { useChatStore } from '../store/chatStore';
|
2026-01-07 09:32:53 +00:00
|
|
|
import { Theme } from 'emoji-picker-react';
|
2026-01-04 00:41:51 +00:00
|
|
|
import { useDropzone } from 'react-dropzone';
|
|
|
|
|
import { apiClient } from '@/services/api/client';
|
|
|
|
|
import { MessageAttachment } from '../types';
|
2026-01-07 09:32:53 +00:00
|
|
|
import { LoadingSpinner } from '@/components/ui/loading-spinner';
|
|
|
|
|
import { logger } from '@/utils/logger';
|
2026-01-11 02:20:52 +00:00
|
|
|
import { cn } from '@/lib/utils';
|
2026-01-25 11:33:46 +00:00
|
|
|
import { Button } from '@/components/ui/button';
|
2026-01-15 19:01:47 +00:00
|
|
|
import { useIsRateLimited } from '@/hooks/useIsRateLimited';
|
2026-01-11 02:20:52 +00:00
|
|
|
|
|
|
|
|
// Lazy load
|
2026-01-13 18:47:57 +00:00
|
|
|
const EmojiPicker = lazy(() =>
|
|
|
|
|
import('emoji-picker-react').then((module) => ({ default: module.default })),
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
export const ChatInput: React.FC = () => {
|
|
|
|
|
const [message, setMessage] = useState('');
|
2026-01-04 00:41:51 +00:00
|
|
|
const [attachments, setAttachments] = useState<MessageAttachment[]>([]);
|
|
|
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
|
|
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
|
|
|
const { sendMessage, setTyping } = useChat();
|
2025-12-03 21:56:50 +00:00
|
|
|
const { currentConversationId } = useChatStore();
|
2026-01-15 19:01:47 +00:00
|
|
|
const isRateLimited = useIsRateLimited();
|
2026-01-04 00:41:51 +00:00
|
|
|
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
2026-01-04 00:41:51 +00:00
|
|
|
if ((message.trim() || attachments.length > 0) && currentConversationId) {
|
|
|
|
|
sendMessage(message, attachments.length > 0 ? attachments : undefined);
|
2025-12-03 21:56:50 +00:00
|
|
|
setMessage('');
|
2026-01-04 00:41:51 +00:00
|
|
|
setAttachments([]);
|
|
|
|
|
|
2026-01-11 02:20:52 +00:00
|
|
|
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
2026-01-04 00:41:51 +00:00
|
|
|
setTyping(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
|
|
|
|
const onDrop = useCallback(async (acceptedFiles: File[]) => {
|
|
|
|
|
setIsUploading(true);
|
|
|
|
|
try {
|
|
|
|
|
const uploadPromises = acceptedFiles.map(async (file) => {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append('file', file);
|
|
|
|
|
const response = await apiClient.post('/uploads', formData, {
|
2026-01-13 18:47:57 +00:00
|
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
2026-01-04 00:41:51 +00:00
|
|
|
});
|
|
|
|
|
const data = response.data;
|
|
|
|
|
return {
|
|
|
|
|
file_name: file.name,
|
|
|
|
|
file_type: file.type,
|
2026-01-11 02:20:52 +00:00
|
|
|
file_url: data.url,
|
2026-01-04 00:41:51 +00:00
|
|
|
file_size: file.size,
|
|
|
|
|
} as MessageAttachment;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const newAttachments = await Promise.all(uploadPromises);
|
|
|
|
|
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
|
|
|
} catch (error) {
|
2026-01-07 09:32:53 +00:00
|
|
|
logger.error('Failed to upload files', {
|
2026-01-13 18:47:57 +00:00
|
|
|
error: error instanceof Error ? error.message : String(error),
|
2026-01-07 09:32:53 +00:00
|
|
|
});
|
2026-01-04 00:41:51 +00:00
|
|
|
} finally {
|
|
|
|
|
setIsUploading(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
|
|
|
onDrop,
|
2026-01-11 02:20:52 +00:00
|
|
|
noClick: true,
|
2026-01-04 00:41:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const handleEmojiClick = (emojiData: { emoji: string }) => {
|
|
|
|
|
setMessage((prev) => prev + emojiData.emoji);
|
|
|
|
|
setShowEmojiPicker(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeAttachment = (index: number) => {
|
|
|
|
|
setAttachments((prev) => prev.filter((_, i) => i !== index));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (message.length > 0) {
|
|
|
|
|
setTyping(true);
|
2026-01-11 02:20:52 +00:00
|
|
|
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
2026-01-04 00:41:51 +00:00
|
|
|
typingTimeoutRef.current = setTimeout(() => {
|
|
|
|
|
setTyping(false);
|
2026-01-11 02:20:52 +00:00
|
|
|
}, 3000);
|
2026-01-04 00:41:51 +00:00
|
|
|
} else {
|
|
|
|
|
setTyping(false);
|
|
|
|
|
}
|
|
|
|
|
}, [message, setTyping]);
|
|
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
return (
|
2026-01-11 02:20:52 +00:00
|
|
|
<div {...getRootProps()} className="relative">
|
|
|
|
|
<input {...getInputProps()} ref={fileInputRef} className="hidden" />
|
|
|
|
|
|
|
|
|
|
{/* Upload Overlay */}
|
|
|
|
|
{isDragActive && (
|
2026-02-08 23:08:42 +00:00
|
|
|
<div className="absolute bottom-full left-0 right-0 h-48 z-50 bg-muted/10 backdrop-blur-md flex items-center justify-center border-t-2 border-border border-dashed rounded-t-2xl animate-fadeIn">
|
2026-01-11 02:20:52 +00:00
|
|
|
<div className="text-center">
|
2026-02-08 23:08:42 +00:00
|
|
|
<div className="w-12 h-12 rounded-full bg-muted/20 flex items-center justify-center mx-auto mb-2 animate-bounce">
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<Paperclip className="w-6 h-6 text-muted-foreground" />
|
2026-01-11 02:20:52 +00:00
|
|
|
</div>
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<p className="text-muted-foreground font-mono uppercase tracking-widest text-sm">
|
2026-01-13 18:47:57 +00:00
|
|
|
Initiate Data Transfer
|
|
|
|
|
</p>
|
2026-01-11 02:20:52 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Attachments Preview */}
|
2026-01-04 00:41:51 +00:00
|
|
|
{attachments.length > 0 && (
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<div className="absolute bottom-full left-0 right-0 p-4 bg-background/90 backdrop-blur-xl border-t border-white/10 flex gap-2 overflow-x-auto">
|
2026-01-04 00:41:51 +00:00
|
|
|
{attachments.map((att, i) => (
|
2026-01-13 18:47:57 +00:00
|
|
|
<div
|
|
|
|
|
key={i}
|
refactor: Phase 6 — Migrate feature modules to SUMI tokens
- auth: Replace gray-* with muted/border tokens, text-foreground
- settings: TwoFactorSettings + NotificationSettings text-foreground
- chat: ChatInput, ConversationItem, ChatPage — text-foreground,
remove kodo references
- player: PlayerExpanded, PlayerQueue, PlayerControls — text-foreground,
remove cyan/magenta gradients
- playlists: All components — text-foreground for badges/headings
- tracks: TrackCard, TrackListRow — text-foreground, remove glow effects
- studio: FileGridCard — text-foreground
- library: LibraryPageGrid — remove hover-glow-cyan, shadow-card-glow-cyan
- profile: UserProfilePageHeader — text-foreground
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:06:28 +00:00
|
|
|
className="relative group flex items-center gap-2 p-2 bg-white/5 rounded-lg border border-white/10 text-xs text-foreground min-w-36"
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
2026-01-04 00:41:51 +00:00
|
|
|
{att.file_type.startsWith('image') ? (
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
<ImageIcon size={14} className="text-primary" />
|
2026-01-04 00:41:51 +00:00
|
|
|
) : (
|
2026-02-08 23:13:27 +00:00
|
|
|
<File size={14} className="text-muted-foreground" />
|
2026-01-04 00:41:51 +00:00
|
|
|
)}
|
2026-01-11 02:20:52 +00:00
|
|
|
<span className="truncate flex-1">{att.file_name}</span>
|
2026-01-04 00:41:51 +00:00
|
|
|
<button
|
|
|
|
|
onClick={() => removeAttachment(i)}
|
2026-02-08 23:14:40 +00:00
|
|
|
className="p-1 hover:bg-white/10 rounded-full text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
|
2026-01-04 00:41:51 +00:00
|
|
|
>
|
|
|
|
|
<X size={12} />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-01-11 02:20:52 +00:00
|
|
|
<form onSubmit={handleSubmit} className="flex items-center gap-2">
|
2026-01-04 00:41:51 +00:00
|
|
|
<div className="flex gap-1">
|
2026-01-11 02:20:52 +00:00
|
|
|
<Button
|
2026-01-04 00:41:51 +00:00
|
|
|
type="button"
|
2026-01-11 02:20:52 +00:00
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
refactor: Phase 6 — Migrate feature modules to SUMI tokens
- auth: Replace gray-* with muted/border tokens, text-foreground
- settings: TwoFactorSettings + NotificationSettings text-foreground
- chat: ChatInput, ConversationItem, ChatPage — text-foreground,
remove kodo references
- player: PlayerExpanded, PlayerQueue, PlayerControls — text-foreground,
remove cyan/magenta gradients
- playlists: All components — text-foreground for badges/headings
- tracks: TrackCard, TrackListRow — text-foreground, remove glow effects
- studio: FileGridCard — text-foreground
- library: LibraryPageGrid — remove hover-glow-cyan, shadow-card-glow-cyan
- profile: UserProfilePageHeader — text-foreground
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:06:28 +00:00
|
|
|
className="text-muted-foreground hover:text-foreground hover:bg-white/5"
|
2026-01-11 02:20:52 +00:00
|
|
|
onClick={() => fileInputRef.current?.click()}
|
2026-01-04 00:41:51 +00:00
|
|
|
>
|
|
|
|
|
<Paperclip size={20} />
|
2026-01-11 02:20:52 +00:00
|
|
|
</Button>
|
2026-01-04 00:41:51 +00:00
|
|
|
|
|
|
|
|
<div className="relative">
|
2026-01-11 02:20:52 +00:00
|
|
|
<Button
|
2026-01-04 00:41:51 +00:00
|
|
|
type="button"
|
2026-01-11 02:20:52 +00:00
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
2026-01-04 00:41:51 +00:00
|
|
|
className={cn(
|
refactor: Phase 6 — Migrate feature modules to SUMI tokens
- auth: Replace gray-* with muted/border tokens, text-foreground
- settings: TwoFactorSettings + NotificationSettings text-foreground
- chat: ChatInput, ConversationItem, ChatPage — text-foreground,
remove kodo references
- player: PlayerExpanded, PlayerQueue, PlayerControls — text-foreground,
remove cyan/magenta gradients
- playlists: All components — text-foreground for badges/headings
- tracks: TrackCard, TrackListRow — text-foreground, remove glow effects
- studio: FileGridCard — text-foreground
- library: LibraryPageGrid — remove hover-glow-cyan, shadow-card-glow-cyan
- profile: UserProfilePageHeader — text-foreground
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:06:28 +00:00
|
|
|
'text-muted-foreground hover:text-foreground hover:bg-white/5',
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
showEmojiPicker && 'text-muted-foreground bg-white/5',
|
2026-01-04 00:41:51 +00:00
|
|
|
)}
|
|
|
|
|
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
|
|
|
|
>
|
|
|
|
|
<Smile size={20} />
|
2026-01-11 02:20:52 +00:00
|
|
|
</Button>
|
2026-01-04 00:41:51 +00:00
|
|
|
|
|
|
|
|
{showEmojiPicker && (
|
2026-01-11 02:20:52 +00:00
|
|
|
<div className="absolute bottom-full left-0 mb-4 z-50 animate-scaleIn origin-bottom-left">
|
2026-01-13 18:47:57 +00:00
|
|
|
<div
|
|
|
|
|
className="fixed inset-0"
|
|
|
|
|
onClick={() => setShowEmojiPicker(false)}
|
|
|
|
|
/>
|
2026-01-11 02:20:52 +00:00
|
|
|
<div className="relative shadow-2xl rounded-xl overflow-hidden border border-white/10">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Suspense
|
|
|
|
|
fallback={
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<div className="w-[350px] h-[450px] bg-card flex items-center justify-center">
|
2026-01-13 18:47:57 +00:00
|
|
|
<LoadingSpinner />
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
2026-01-07 09:32:53 +00:00
|
|
|
<EmojiPicker
|
|
|
|
|
onEmojiClick={handleEmojiClick}
|
2026-01-11 02:20:52 +00:00
|
|
|
theme={Theme.DARK}
|
2026-01-07 09:32:53 +00:00
|
|
|
lazyLoadEmojis={true}
|
2026-01-11 02:20:52 +00:00
|
|
|
width={350}
|
|
|
|
|
height={450}
|
2026-01-07 09:32:53 +00:00
|
|
|
/>
|
|
|
|
|
</Suspense>
|
2026-01-04 00:41:51 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-11 02:20:52 +00:00
|
|
|
<div className="flex-1 relative">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={message}
|
|
|
|
|
onChange={(e) => setMessage(e.target.value)}
|
|
|
|
|
placeholder="Broadcast message..."
|
refactor: Phase 6 — Migrate feature modules to SUMI tokens
- auth: Replace gray-* with muted/border tokens, text-foreground
- settings: TwoFactorSettings + NotificationSettings text-foreground
- chat: ChatInput, ConversationItem, ChatPage — text-foreground,
remove kodo references
- player: PlayerExpanded, PlayerQueue, PlayerControls — text-foreground,
remove cyan/magenta gradients
- playlists: All components — text-foreground for badges/headings
- tracks: TrackCard, TrackListRow — text-foreground, remove glow effects
- studio: FileGridCard — text-foreground
- library: LibraryPageGrid — remove hover-glow-cyan, shadow-card-glow-cyan
- profile: UserProfilePageHeader — text-foreground
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:06:28 +00:00
|
|
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:border-border/50 focus:ring-1 focus:ring-border/50 transition-all font-mono text-sm"
|
2026-01-11 02:20:52 +00:00
|
|
|
disabled={!currentConversationId || isUploading}
|
|
|
|
|
/>
|
|
|
|
|
{message.length === 0 && !isUploading && (
|
2026-01-26 13:12:17 +00:00
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
refactor: Phase 6 — Migrate feature modules to SUMI tokens
- auth: Replace gray-* with muted/border tokens, text-foreground
- settings: TwoFactorSettings + NotificationSettings text-foreground
- chat: ChatInput, ConversationItem, ChatPage — text-foreground,
remove kodo references
- player: PlayerExpanded, PlayerQueue, PlayerControls — text-foreground,
remove cyan/magenta gradients
- playlists: All components — text-foreground for badges/headings
- tracks: TrackCard, TrackListRow — text-foreground, remove glow effects
- studio: FileGridCard — text-foreground
- library: LibraryPageGrid — remove hover-glow-cyan, shadow-card-glow-cyan
- profile: UserProfilePageHeader — text-foreground
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:06:28 +00:00
|
|
|
className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 text-muted-foreground/30 hover:text-foreground"
|
2026-01-26 13:12:17 +00:00
|
|
|
>
|
|
|
|
|
<Mic className="w-4 h-4" />
|
|
|
|
|
</Button>
|
2026-01-11 02:20:52 +00:00
|
|
|
)}
|
|
|
|
|
</div>
|
2026-01-04 00:41:51 +00:00
|
|
|
|
2026-01-11 02:20:52 +00:00
|
|
|
<Button
|
2026-01-04 00:41:51 +00:00
|
|
|
type="submit"
|
2026-01-18 21:27:53 +00:00
|
|
|
variant="primary"
|
2026-01-11 02:20:52 +00:00
|
|
|
size="icon"
|
|
|
|
|
className={cn(
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
'rounded-xl transition-all duration-[var(--duration-normal)]',
|
2026-01-13 18:47:57 +00:00
|
|
|
message.trim() || attachments.length > 0
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
? 'bg-primary text-foreground hover:bg-primary-dim shadow-neon-cyan'
|
2026-02-08 23:13:27 +00:00
|
|
|
: 'bg-white/5 text-muted-foreground hover:bg-white/10',
|
2026-01-11 02:20:52 +00:00
|
|
|
)}
|
2026-01-13 18:47:57 +00:00
|
|
|
disabled={
|
|
|
|
|
!currentConversationId ||
|
|
|
|
|
(!message.trim() && attachments.length === 0) ||
|
2026-01-15 19:01:47 +00:00
|
|
|
isUploading ||
|
|
|
|
|
isRateLimited
|
2026-01-13 18:47:57 +00:00
|
|
|
}
|
2026-01-04 00:41:51 +00:00
|
|
|
>
|
|
|
|
|
{isUploading ? (
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<div className="w-5 h-5 border-2 border-border/30 border-t-border rounded-full animate-spin" />
|
2026-01-04 00:41:51 +00:00
|
|
|
) : (
|
2026-01-13 18:47:57 +00:00
|
|
|
<Send
|
|
|
|
|
size={18}
|
|
|
|
|
className={cn(message.trim() ? 'translate-x-0.5' : '')}
|
|
|
|
|
/>
|
2026-01-04 00:41:51 +00:00
|
|
|
)}
|
2026-01-11 02:20:52 +00:00
|
|
|
</Button>
|
2026-01-04 00:41:51 +00:00
|
|
|
</form>
|
|
|
|
|
</div>
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
};
|