diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 7d93dd5acc6..4b79629731e 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -277,6 +277,11 @@ function resolveAssistantAvatarUrl(state: AppViewState): string | undefined { } export function renderApp(state: AppViewState) { + const requestHostUpdate = + typeof (state as { requestUpdate?: unknown }).requestUpdate === "function" + ? () => (state as { requestUpdate: () => void }).requestUpdate() + : undefined; + // Gate: require successful gateway connection before showing the dashboard. // The gateway URL confirmation overlay is always rendered so URL-param flows still work. if (!state.connected) { @@ -1323,7 +1328,9 @@ export function renderApp(state: AppViewState) { }); }, onChatScroll: (event) => state.handleChatScroll(event), + getDraft: () => state.chatMessage, onDraftChange: (next) => (state.chatMessage = next), + onRequestUpdate: requestHostUpdate, attachments: state.chatAttachments, onAttachmentsChange: (next) => (state.chatAttachments = next), onSend: () => state.handleSendChat(), diff --git a/ui/src/ui/chat/pinned-messages.ts b/ui/src/ui/chat/pinned-messages.ts index 4914b0db32a..a3e77a9483b 100644 --- a/ui/src/ui/chat/pinned-messages.ts +++ b/ui/src/ui/chat/pinned-messages.ts @@ -56,6 +56,10 @@ export class PinnedMessages { } private save(): void { - localStorage.setItem(this.key, JSON.stringify([...this._indices])); + try { + localStorage.setItem(this.key, JSON.stringify([...this._indices])); + } catch { + // ignore + } } } diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index 8d070812c63..3559184e38d 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -80,7 +80,9 @@ export type ChatProps = { onScrollToBottom?: () => void; onRefresh: () => void; onToggleFocusMode: () => void; + getDraft?: () => string; onDraftChange: (next: string) => void; + onRequestUpdate?: () => void; onSend: () => void; onAbort?: () => void; onQueueRemove: (id: string) => void; @@ -803,12 +805,8 @@ export function renderChat(props: ChatProps) { : `Message ${props.assistantName || "agent"} (Enter to send)` : "Connect to the gateway to start chatting..."; - // We need a requestUpdate shim since we're in functional mode: - // the host Lit component will re-render on state change anyway, - // so we trigger by calling onDraftChange with current value. - const requestUpdate = () => { - props.onDraftChange(props.draft); - }; + const requestUpdate = props.onRequestUpdate ?? (() => {}); + const getDraft = props.getDraft ?? (() => props.draft); const splitRatio = props.splitRatio ?? 0.6; const sidebarOpen = Boolean(props.sidebarOpen && props.onCloseSidebar); @@ -1020,9 +1018,6 @@ export function renderChat(props: ChatProps) { adjustTextareaHeight(target); updateSlashMenu(target.value, requestUpdate); inputHistory.reset(); - // onDraftChange must be last: requestUpdate() inside updateSlashMenu - // uses the stale render-time props.draft, overwriting chatMessage. - // Calling onDraftChange last ensures the correct DOM value wins. props.onDraftChange(target.value); }; @@ -1192,7 +1187,7 @@ export function renderChat(props: ChatProps) { const started = startStt({ onTranscript: (text, isFinal) => { if (isFinal) { - const current = props.draft; + const current = getDraft(); const sep = current && !current.endsWith(" ") ? " " : ""; props.onDraftChange(current + sep + text); sttInterimText = "";