ui: Avatar + Badge + Tooltip + @mention autocomplete

- MessageItem: shadcn Avatar (square, brutalist), Badge for AGT/type labels
- Tooltip on timestamps: tap/hover shows full date + seq number
- ComposeBox: typing @ opens mention autocomplete popup
  - Arrow keys to navigate, Enter/Tab to select, Escape to close
  - Shows username, display name, role
  - Fetches user list on mount
- More breathing room in messages (py-3/4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 21:05:30 +02:00
parent 98a46b962f
commit 6ba6719932
10 changed files with 1338 additions and 24 deletions

View File

@@ -1,6 +1,9 @@
import { useState } from "react";
import type { Message } from "@/types/Message";
import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
interface Props {
message: Message;
@@ -68,7 +71,17 @@ export function MessageItem({ message, replyTarget, onReply }: Props) {
<div className="px-4 py-3 md:px-5 md:py-4">
{/* Header */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-2.5 text-[11px] flex-wrap">
{/* Avatar */}
<Avatar size="sm" className="rounded-none">
<AvatarFallback className={cn(
"rounded-none font-mono text-[10px] font-bold",
isAgent ? "bg-primary/20 text-primary" : "bg-muted text-muted-foreground"
)}>
{message.user.display_name[0]}
</AvatarFallback>
</Avatar>
{/* Name */}
<span className={cn(
"font-sans font-bold text-xs",
@@ -79,32 +92,34 @@ export function MessageItem({ message, replyTarget, onReply }: Props) {
{/* Agent badge */}
{isAgent && (
<span className="font-mono text-[9px] font-bold px-1.5 py-0.5 bg-primary/15 text-primary uppercase tracking-wider">
<Badge variant="outline" className="font-mono text-[9px] font-bold px-1.5 py-0 h-4 rounded-none border-primary/30 text-primary uppercase tracking-wider">
AGT
</span>
</Badge>
)}
{/* Type badge */}
{cfg.label && (
<span className={cn("font-mono text-[9px] font-bold px-1.5 py-0.5 uppercase tracking-wider", cfg.labelBg)}>
<Badge variant="secondary" className={cn("font-mono text-[9px] font-bold px-1.5 py-0 h-4 rounded-none uppercase tracking-wider", cfg.labelBg)}>
{cfg.label}
</span>
</Badge>
)}
{/* Time */}
<span className="text-muted-foreground font-mono tabular-nums md:hidden text-[10px]">
{timeAgo(message.created_at)}
</span>
<span className="text-muted-foreground font-mono tabular-nums hidden md:inline text-[10px]">
{new Date(message.created_at).toLocaleTimeString("en-US", {
hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit",
})}
</span>
{/* Seq */}
<span className="text-muted-foreground/30 font-mono tabular-nums text-[10px] hidden md:inline">
#{Number(message.seq)}
</span>
{/* Time — tooltip shows full timestamp on mobile */}
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-muted-foreground font-mono tabular-nums text-[10px] cursor-default">
<span className="md:hidden">{timeAgo(message.created_at)}</span>
<span className="hidden md:inline">
{new Date(message.created_at).toLocaleTimeString("en-US", {
hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit",
})}
</span>
</TooltipTrigger>
<TooltipContent className="font-mono text-[10px] rounded-none">
{new Date(message.created_at).toLocaleString()} · seq #{Number(message.seq)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/* Reply button */}
<button