From d7f5cd544306a07f770592530a907a931fe51a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 6 Aug 2026 23:09:47 +0000 Subject: [PATCH] fix the chat's dark mode, dead air and overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the provider label was bg-duck-dark/80 text-white, and --duck-dark is near-white in dark mode, so it was white on white for every session that had already started. same class on the active provider tab. the streaming bubble bailed on empty text, so the wait between send and the first token — tens of seconds with thinking on — rendered nothing at all. it now shows the bubble with pulsing dots. also: tool status tones onto tone.ts instead of a third copy of the same green/red/amber ternary, sub-12px labels up to text-xs, break-all to break-words so shell commands stop breaking mid-identifier, and break-words on the user bubble so a pasted url stays inside the pane. Co-Authored-By: Claude Opus 5 --- .../apps/Chat/components/MessageBubble.tsx | 70 ++++++++++++------- .../src/apps/Chat/components/MessageList.tsx | 3 +- .../apps/Chat/components/ModelSelector.tsx | 27 ++++--- .../src/apps/Chat/components/ToolActivity.tsx | 43 ++++++------ 4 files changed, 83 insertions(+), 60 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/Chat/components/MessageBubble.tsx b/src/workspaces/officerdev/src/apps/Chat/components/MessageBubble.tsx index 4b16e31e..90df7636 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/MessageBubble.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/MessageBubble.tsx @@ -4,6 +4,8 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import { Volume2, Loader2, Square, Clock, Check, X, CircleSlash } from 'lucide-react'; +import type { Tone } from '@/components/Data'; +import { toneText } from '@/components/Data'; import type { ChatMessage } from '../types'; import { ToolActivity } from './ToolActivity'; import { QuestionActivity } from './QuestionActivity'; @@ -126,11 +128,12 @@ export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => { <> {metadata && }
-
+
{message.images?.map((img, i) => ( {img.filename} ))} -
{text}
+ {/* break-words, or a pasted URL or base64 blob pushes the bubble past max-w and out of the pane. */} +
{text}
@@ -174,7 +177,7 @@ export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => { case 'result': return (
- + Done · ${message.cost.totalUSD.toFixed(3)} · {message.cost.inputTokens + message.cost.outputTokens} tokens
@@ -186,7 +189,7 @@ export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => { case 'error': return (
-
+
{message.text}
@@ -206,24 +209,18 @@ export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => { const TaskActivity = ({ message }: { message: Extract }) => { const { status, summary, description, taskType } = message; const Icon = status === 'completed' ? Check : status === 'failed' ? X : status === 'stopped' ? CircleSlash : Clock; - const tone = - status === 'completed' - ? 'text-green-600' - : status === 'failed' - ? 'text-red-600' - : status === 'stopped' - ? 'text-duck-dark/50' - : 'text-amber-500'; + const tone: Tone = + status === 'completed' ? 'success' : status === 'failed' ? 'danger' : status === 'stopped' ? 'neutral' : 'warning'; return ( -
- +
+
-
+
{description || 'Background task'} - {taskType && {taskType}} + {taskType && {taskType}}
-
+
{status ? summary || `Task ${status}.` : 'Running in the background…'}
@@ -231,21 +228,44 @@ const TaskActivity = ({ message }: { message: Extract ( + + {[0, 150, 300].map((delay) => ( + + ))} + +); + type StreamingBubbleProps = { text: string; }; export const StreamingBubble = ({ text }: StreamingBubbleProps) => { - if (!text) return null; return (
-
-
- - {injectImages(text)} - - -
+
+ {text ? ( +
+ + {injectImages(text)} + + +
+ ) : ( + + )}
); diff --git a/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx b/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx index 1d251be9..6818ce43 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx @@ -101,7 +101,8 @@ export const MessageList = ({ manager }: MessageListProps) => { {showJumpToBottom && ( diff --git a/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx b/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx index be5f042d..43f46a09 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx @@ -90,21 +90,26 @@ export function ModelSelector({ }; return ( -
+
{isLocked ? ( - + // A static label, not an action — `bg-muted` rather than a filled accent. It used to be + // `bg-duck-dark/80 text-white`, and --duck-dark is near-white in dark mode, so this was white + // on white for every session that had already started, i.e. almost always. + {activeProvider ? displayName(activeProvider) : 'Chat'} ) : ( -
+ // Fifteen providers can be configured and the names are long ("Google Vertex AI"), so the strip + // scrolls rather than crushing the model select beside it. +
{providers.map((provider) => (
)} -
+
{availableModels.find((m) => m.id === displayModel)?.reasoning && ( )} -
+
{providerModels.length > 0 ? (