From 10c8454c9b7f3f5cd83dc96863e0f778eff726fe Mon Sep 17 00:00:00 2001 From: BOHA Date: Thu, 11 Jun 2026 00:11:31 +0200 Subject: [PATCH] fix(odin): clear composer on send + readable selection in user bubble - the sent message now leaves the input the moment the optimistic bubble appears (restored along with attachments if the request fails) - text selected in the primary-filled user bubble inverts to white-on-primary instead of vanishing into the global ::selection style Co-Authored-By: Claude Fable 5 --- src/admin/components/odin/OdinChat.tsx | 12 +++++++++--- src/admin/components/odin/OdinThread.tsx | 9 +++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/admin/components/odin/OdinChat.tsx b/src/admin/components/odin/OdinChat.tsx index 53f50cb..c890d88 100644 --- a/src/admin/components/odin/OdinChat.tsx +++ b/src/admin/components/odin/OdinChat.tsx @@ -216,6 +216,8 @@ export default function OdinChat() { // tools over system data); attachments keep the invoice-extract path. setBusy(true); const prevTurns = turns; + const prevInput = input; + const prevAttachments = attachments; const userContent = [ text, @@ -228,6 +230,10 @@ export default function OdinChat() { { role: "user", content: userContent }, ]; setTurns(optimistic); + // Clear the composer immediately — the message now lives in the thread + // as the optimistic bubble; a failure restores both below. + setInput(""); + setAttachments([]); try { if (files.length > 0) { @@ -268,8 +274,6 @@ export default function OdinChat() { setReview((prev) => [...prev, ...reviews]); const note = summaryNote(reviews); setTurns((t) => [...t, { role: "assistant", content: note }]); - setInput(""); - setAttachments([]); // Create the conversation now (first successful message) and persist. const convId = await ensureActive(); if (convId != null) @@ -293,7 +297,6 @@ export default function OdinChat() { ? body.data.tool_trace : undefined; setTurns((t) => [...t, { role: "assistant", content: reply, tools }]); - setInput(""); // Create the conversation now (first successful message) and persist. const convId = await ensureActive(); if (convId != null) @@ -305,6 +308,9 @@ export default function OdinChat() { } } catch (e) { setTurns(prevTurns); + // Give the user their message back to retry/edit. + setInput(prevInput); + setAttachments(prevAttachments); const fallback = files.length > 0 ? "Chyba čtení faktur" : "Chyba AI"; alert.error(e instanceof Error ? e.message : fallback); } finally { diff --git a/src/admin/components/odin/OdinThread.tsx b/src/admin/components/odin/OdinThread.tsx index 9cb57e5..1d381e1 100644 --- a/src/admin/components/odin/OdinThread.tsx +++ b/src/admin/components/odin/OdinThread.tsx @@ -196,6 +196,15 @@ export default function OdinThread({ borderRadius: 2, boxShadow: 1, bgcolor: isUser ? "primary.main" : "background.paper", + // The global ::selection is primary-on-white — invisible on + // this primary-filled bubble; invert it so selected/copied + // text stays readable. + ...(isUser && { + "& ::selection": { + backgroundColor: "common.white", + color: "primary.main", + }, + }), }} > {/* Tools the assistant consulted for this turn (Phase 2a). */}