1+ import { isDurableObjectResetError , isTransientRpcError , logRpcFailure , reportDoResetError } from "./rpcErrors" ;
12import {
23 Fragment ,
34 memo ,
@@ -1780,6 +1781,7 @@ export const ChatInput = ({
17801781 attachLabel,
17811782 draftUpdateBanner,
17821783 blockedReason,
1784+ chatKey,
17831785 onStop,
17841786 showThinkingTraces = true ,
17851787 onToggleThinkingTraces,
@@ -1825,6 +1827,8 @@ export const ChatInput = ({
18251827 /** When set, the composer is disabled and shows this message — the user must resolve something
18261828 * (e.g. accept/deny a pending connection request) before they can type or send. */
18271829 blockedReason ?: string ;
1830+ /** Identity of the chat the composer is bound to; a change clears chat-scoped hints. */
1831+ chatKey ?: number | null ;
18281832 onStop ?: ( ) => void ;
18291833 showThinkingTraces ?: boolean ;
18301834 onToggleThinkingTraces ?: ( ) => void ;
@@ -1838,6 +1842,9 @@ export const ChatInput = ({
18381842 const [ capsules , setCapsules ] = useState < InputCapsule [ ] > ( [ ] ) ;
18391843 const [ pendingAttachments , setPendingAttachments ] = useState < PendingAttachment [ ] > ( [ ] ) ;
18401844 const [ isSending , setIsSending ] = useState ( false ) ;
1845+ const [ sendHiccup , setSendHiccup ] = useState ( false ) ;
1846+
1847+ useEffect ( ( ) => setSendHiccup ( false ) , [ chatKey ] ) ;
18411848 const [ isAttachmentDragActive , setIsAttachmentDragActive ] = useState ( false ) ;
18421849 const [ selectedSlashCommand , setSelectedSlashCommand ] = useState < SelectedSlashCommand | null > ( null ) ;
18431850 // The caret the slash command picker parses at. Deliberately updated only when it moves to a
@@ -2276,6 +2283,7 @@ export const ChatInput = ({
22762283
22772284 const handleSend = async ( ) => {
22782285 if ( sendInFlightRef . current || isSending || isBlocked ) return ;
2286+ setSendHiccup ( false ) ;
22792287 const attachmentsSnapshot = pendingAttachments ;
22802288 const readyAttachments = attachmentsSnapshot
22812289 . filter ( ( attachment ) => attachment . uploadState === "ready" && attachment . ref )
@@ -2455,7 +2463,8 @@ export const ChatInput = ({
24552463
24562464 const submitMessage = ( ) => {
24572465 void handleSend ( ) . catch ( ( err ) => {
2458- console . error ( "Failed to send chat message:" , err ) ;
2466+ // The onSend handlers already log; the composer only needs the hint state.
2467+ if ( isTransientRpcError ( err ) ) setSendHiccup ( true ) ;
24592468 } ) ;
24602469 } ;
24612470
@@ -3050,6 +3059,11 @@ export const ChatInput = ({
30503059 </ div >
30513060 ) }
30523061 { draftUpdateBanner }
3062+ { sendHiccup && (
3063+ < div className = "px-4 pt-2 text-xs text-kumo-warning" >
3064+ Connection hiccup — your message may not have been sent. Check the thread, then try again.
3065+ </ div >
3066+ ) }
30533067 { /* Textarea */ }
30543068 < div className = "relative px-4 pb-1 pt-3" >
30553069 { slashCommandPicker . popup }
@@ -5213,9 +5227,10 @@ function ChatInterface({
52135227 forceUpdate ( ) ;
52145228 }
52155229 } catch ( err ) {
5216- console . error ( "Failed to subscribe to chats:" , err ) ;
5217- reportIssue ( 'chat.subscription-load' , err )
5218- toasts . add ( { title : "Unable to load conversations" , variant : "error" } ) ;
5230+ if ( ! logRpcFailure ( "Failed to subscribe to chats:" , err ) ) {
5231+ reportIssue ( 'chat.subscription-load' , err )
5232+ toasts . add ( { title : "Unable to load conversations" , variant : "error" } ) ;
5233+ }
52195234 }
52205235 } ;
52215236
@@ -5366,8 +5381,10 @@ function ChatInterface({
53665381 ) ;
53675382 }
53685383 } catch ( err ) {
5369- console . error ( "Failed to send message:" , err ) ;
5370- toasts . add ( { title : "Failed to send message" , variant : "error" } ) ;
5384+ if ( isDurableObjectResetError ( err ) ) reportDoResetError ( "chat.send" , err ) ;
5385+ if ( ! logRpcFailure ( "Failed to send message:" , err ) ) {
5386+ toasts . add ( { title : "Failed to send message" , variant : "error" } ) ;
5387+ }
53715388 throw err ;
53725389 }
53735390 } ;
@@ -5388,8 +5405,10 @@ function ChatInterface({
53885405 message , model , capsules , attachments , formats ) ;
53895406 onNavigateToChatRef . current ( newChatId ) ;
53905407 } catch ( err ) {
5391- console . error ( "Failed to create new chat:" , err ) ;
5392- toasts . add ( { title : "Failed to start conversation" , variant : "error" } ) ;
5408+ if ( isDurableObjectResetError ( err ) ) reportDoResetError ( "chat.new" , err ) ;
5409+ if ( ! logRpcFailure ( "Failed to create new chat:" , err ) ) {
5410+ toasts . add ( { title : "Failed to start conversation" , variant : "error" } ) ;
5411+ }
53935412 throw err ;
53945413 }
53955414 } ;
@@ -7576,6 +7595,7 @@ function ChatInterface({
75767595 < div className = { `flex-shrink-0 bg-kumo-base ${ sidebarMode ? "" : "border-t border-kumo-line" } ` } >
75777596 < div className = { useConstrainedChatWidth ? "mx-auto w-full max-w-[920px]" : "" } >
75787597 < ChatInput
7598+ chatKey = { selectedChatId }
75797599 createCapsuleGatekeeper = { ( accountId , url ) =>
75807600 overseer . newGatekeeper ( accountId , url )
75817601 }
0 commit comments