From 5dd8c85ce4d2935708b1ebe074f73310d64ca43f Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 9 Jul 2026 18:42:47 +0530 Subject: [PATCH] style(x): chat empty state, message bubbles, sidebar hover gap - Chat empty state: icon-free heading, single suggestion list with leading icons and hover arrows, aligned to the composer column - User message bubbles: rounded-2xl with flattened top-right corner, hover-only copy button under the bubble's bottom-right - Sidebar: hairline gap between menu items so adjacent active/hover highlights don't merge --- apps/x/apps/renderer/src/App.tsx | 63 ++++++++++--------- .../src/components/ai-elements/message.tsx | 35 ++++++++++- .../src/components/chat-empty-state.tsx | 56 +++++++---------- .../renderer/src/components/ui/sidebar.tsx | 2 +- 4 files changed, 94 insertions(+), 62 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 2952b7d0..f401fb0f 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -51,6 +51,7 @@ import { import { Message, MessageContent, + MessageCopyButton, MessageResponse, } from '@/components/ai-elements/message'; import { @@ -5888,14 +5889,17 @@ function App() { {item.content && ( - - - {item.content} - - +
+ + + {item.content} + + + +
)} ) @@ -5903,26 +5907,29 @@ function App() { const { message, files } = parseAttachedFiles(item.content) return ( - - {files.length > 0 && ( -
- {files.map((filePath, index) => ( - - @{wikiLabel(filePath)} - - ))} -
- )} - - {message} - -
+
+ + {files.length > 0 && ( +
+ {files.map((filePath, index) => ( + + @{wikiLabel(filePath)} + + ))} +
+ )} + + {message} + +
+ +
) } diff --git a/apps/x/apps/renderer/src/components/ai-elements/message.tsx b/apps/x/apps/renderer/src/components/ai-elements/message.tsx index ec3acfc1..fcb5250c 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/message.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/message.tsx @@ -14,8 +14,10 @@ import { import { cn } from "@/lib/utils"; import type { FileUIPart, UIMessage } from "ai"; import { + CheckIcon, ChevronLeftIcon, ChevronRightIcon, + CopyIcon, PaperclipIcon, XIcon, } from "lucide-react"; @@ -38,6 +40,37 @@ export const Message = ({ className, from, ...props }: MessageProps) => ( /> ); +/** + * Minimal copy-to-clipboard affordance for a message bubble. Invisible until + * the surrounding Message (`.group`) is hovered. + */ +export const MessageCopyButton = ({ + text, + className, +}: { + text: string; + className?: string; +}) => { + const [copied, setCopied] = useState(false); + return ( + + ); +}; + export type MessageContentProps = HTMLAttributes; export const MessageContent = ({ @@ -49,7 +82,7 @@ export const MessageContent = ({ data-slot="message-content" className={cn( "is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm", - "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground", + "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:rounded-tr-md group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-2.5 group-[.is-user]:text-foreground", "group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground", className )} diff --git a/apps/x/apps/renderer/src/components/chat-empty-state.tsx b/apps/x/apps/renderer/src/components/chat-empty-state.tsx index fca21600..b38e41a5 100644 --- a/apps/x/apps/renderer/src/components/chat-empty-state.tsx +++ b/apps/x/apps/renderer/src/components/chat-empty-state.tsx @@ -1,4 +1,4 @@ -import { Bot, Mail, Sparkles, Telescope } from 'lucide-react' +import { ArrowRight, BookOpen, Mail, Zap } from 'lucide-react' import { cn } from '@/lib/utils' import { ToolConnectionsCard } from '@/components/tool-connections-card' @@ -12,8 +12,8 @@ interface ChatEmptyStateProps { const SUGGESTED_ACTIONS: { icon: typeof Mail; title: string; sub: string; prompt: string }[] = [ { icon: Mail, title: 'Draft a reply', sub: 'to an email', prompt: "Let's draft a reply to [name]'s email" }, - { icon: Bot, title: 'Set up a background agent', sub: 'that automates tasks', prompt: 'Set up a background agent that automates [task]' }, - { icon: Telescope, title: 'Research a topic', sub: 'create a local wiki for me', prompt: 'Research [topic] and create a local wiki for me' }, + { icon: Zap, title: 'Set up a background agent', sub: 'that automates tasks', prompt: 'Set up a background agent that automates [task]' }, + { icon: BookOpen, title: 'Research a topic', sub: 'create a local wiki for me', prompt: 'Research [topic] and create a local wiki for me' }, ] /** @@ -25,37 +25,29 @@ export function ChatEmptyState({ wide = false, }: ChatEmptyStateProps) { return ( -
-
-
- -
-
-
What are we working on?
-
Ask anything, or start with a suggestion.
-
+
+
+
What are we working on?
+
Ask anything, or start with a suggestion.
-
-
- Get started -
-
- {SUGGESTED_ACTIONS.map((action) => ( - - ))} -
+
+ {SUGGESTED_ACTIONS.map((action, i) => ( + + ))}
diff --git a/apps/x/apps/renderer/src/components/ui/sidebar.tsx b/apps/x/apps/renderer/src/components/ui/sidebar.tsx index ffe40957..f3550f72 100644 --- a/apps/x/apps/renderer/src/components/ui/sidebar.tsx +++ b/apps/x/apps/renderer/src/components/ui/sidebar.tsx @@ -462,7 +462,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
    )