diff --git a/FEEDBACK.md b/FEEDBACK.md index 5adc68e..4864a0a 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -664,3 +664,10 @@ - **Status:** done - **Completed:** 2026-02-13 — commit `cbb4611` - **Description:** Markdown unordered lists (- item, * item) are not rendered properly in chat messages. They appear as raw text instead of formatted bullet points. Need to verify remarkGfm/ReactMarkdown config handles list rendering correctly, and ensure CSS styles are applied for ul/ol elements in the markdown-body class. + +## Item #62 +- **Date:** 2026-02-13 +- **Priority:** high +- **Status:** done +- **Completed:** 2026-02-13 — commit `2f25c45` +- **Description:** Textarea has an ugly/thick accent-colored border (cyan) visible in the screenshot. The border around the chat input textarea looks bad — it should be more subtle (thin border, muted color, or no visible border at all). The input area should blend cleanly with its container, not have a glowing cyan outline. diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index 888ab59..db63393 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -119,10 +119,14 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session const visibleMessages = useMemo(() => { const filtered = messages.filter(hasVisibleContent); - return filtered.reduce>((acc, msg) => { + const GROUP_GAP_MS = 2 * 60 * 1000; // 2 minutes + return filtered.reduce>((acc, msg) => { const dk = getDateKey(msg.timestamp); const prevDk = acc.length > 0 ? getDateKey(acc[acc.length - 1].msg.timestamp) : ''; - acc.push({ msg, showSep: dk !== prevDk }); + const showSep = dk !== prevDk; + const prev = acc.length > 0 ? acc[acc.length - 1] : null; + const isFirstInGroup = showSep || !prev || prev.msg.role !== msg.role || prev.msg.isSystemEvent !== msg.isSystemEvent || (msg.timestamp - prev.msg.timestamp > GROUP_GAP_MS); + acc.push({ msg, showSep, isFirstInGroup }); return acc; }, []); }, [messages]); @@ -207,7 +211,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
{t('chat.welcomeSub')}
)} - {visibleMessages.map(({ msg, showSep }) => { + {visibleMessages.map(({ msg, showSep, isFirstInGroup }) => { const isActiveMatch = searchMatches.length > 0 && searchMatches[searchActiveIndex] === msg.id; return (
@@ -219,7 +223,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
)}
- +
); diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index a57912b..dfa1dcf 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -405,7 +405,7 @@ function SystemEventMessage({ message }: { message: ChatMessageType }) { ); } -export const ChatMessageComponent = memo(function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatarUrl }: { message: ChatMessageType; onRetry?: (text: string) => void; agentAvatarUrl?: string }) { +export const ChatMessageComponent = memo(function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatarUrl, isFirstInGroup = true }: { message: ChatMessageType; onRetry?: (text: string) => void; agentAvatarUrl?: string; isFirstInGroup?: boolean }) { useLocale(); // re-render on locale change const { resolvedTheme } = useTheme(); const isLight = resolvedTheme === 'light'; @@ -458,15 +458,16 @@ export const ChatMessageComponent = memo(function ChatMessageComponent({ message } return ( -
- {/* Avatar */} -
- {isUser - ? - : agentAvatarUrl - ? Agent - : - } +
+ {/* Avatar — hidden for grouped messages, but keep width for alignment */} +
+ {isFirstInGroup ? ( + isUser + ? + : agentAvatarUrl + ? Agent + : + ) : null}
{/* Bubble */} diff --git a/src/index.css b/src/index.css index 07e2786..11dd7d1 100644 --- a/src/index.css +++ b/src/index.css @@ -52,6 +52,14 @@ outline-offset: 2px; } +/* Chat textarea: no focus ring (container handles visual feedback) */ +.ht-textarea:focus-visible, +.ht-textarea:focus, +#chat-input:focus-visible, +#chat-input:focus { + outline: none; +} + /* Remove default outline for mouse/touch users */ :focus:not(:focus-visible) { outline: none;