diff --git a/frontend-web/src/components/ai-elements/prompt-input.tsx b/frontend-web/src/components/ai-elements/prompt-input.tsx
index a796b775..58dc06ea 100644
--- a/frontend-web/src/components/ai-elements/prompt-input.tsx
+++ b/frontend-web/src/components/ai-elements/prompt-input.tsx
@@ -56,12 +56,11 @@ import {
type ChangeEventHandler,
Children,
type ClipboardEventHandler,
- type ComponentProps,
- createContext,
- type FormEvent,
- type FormEventHandler,
- Fragment,
- type HTMLAttributes,
+ type ComponentProps,
+ createContext,
+ type FormEvent,
+ type FormEventHandler,
+ type HTMLAttributes,
type KeyboardEventHandler,
type PropsWithChildren,
type ReactNode,
@@ -124,9 +123,11 @@ export const usePromptInputController = () => {
return ctx;
};
-// Optional variants (do NOT throw). Useful for dual-mode components.
-const useOptionalPromptInputController = () =>
- useContext(PromptInputController);
+// Optional variants (do NOT throw). Useful for dual-mode components and for
+// Fast Refresh transitions where a page can briefly retain the previous
+// provider context instance.
+export const useOptionalPromptInputController = () =>
+ useContext(PromptInputController);
export const useProviderAttachments = () => {
const ctx = useContext(ProviderAttachmentsContext);
@@ -403,14 +404,14 @@ export function PromptInputAttachments({
return (
- {attachments.files.map((file) => (
-
- {children(file)}
-
- ))}
-
+ {...props}
+ >
+ {attachments.files.map((file) => (
+
+ {children(file)}
+
+ ))}
+
);
}
diff --git a/frontend-web/src/components/workspace/chats/use-chat-mode.ts b/frontend-web/src/components/workspace/chats/use-chat-mode.ts
index 232fccd6..2f73fac4 100644
--- a/frontend-web/src/components/workspace/chats/use-chat-mode.ts
+++ b/frontend-web/src/components/workspace/chats/use-chat-mode.ts
@@ -1,7 +1,7 @@
import { useParams, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useRef } from "react";
-import { usePromptInputController } from "@/components/ai-elements/prompt-input";
+import { useOptionalPromptInputController } from "@/components/ai-elements/prompt-input";
import { useI18n } from "@/core/i18n/hooks";
/**
@@ -11,7 +11,11 @@ export function useSpecificChatMode() {
const { t } = useI18n();
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
const searchParams = useSearchParams();
- const promptInputController = usePromptInputController();
+ // A regular history item can navigate here while Vite Fast Refresh is
+ // reconciling the previous page tree. In that short window the page may see
+ // a stale provider context. This hook only pre-fills the skill-mode draft,
+ // so it must not make an ordinary chat page fail to render.
+ const promptInputController = useOptionalPromptInputController();
const inputInitialValue = useMemo(() => {
if (threadIdFromPath !== "new" || searchParams.get("mode") !== "skill") {
return undefined;
@@ -19,16 +23,21 @@ export function useSpecificChatMode() {
return t.inputBox.createSkillPrompt;
}, [threadIdFromPath, searchParams, t.inputBox.createSkillPrompt]);
const lastInitialValueRef = useRef(undefined);
- const setInputRef = useRef(promptInputController.textInput.setInput);
- setInputRef.current = promptInputController.textInput.setInput;
+ const setInputRef = useRef<((value: string) => void) | undefined>(
+ promptInputController?.textInput.setInput,
+ );
+ setInputRef.current = promptInputController?.textInput.setInput;
useEffect(() => {
if (
inputInitialValue &&
inputInitialValue !== lastInitialValueRef.current
) {
lastInitialValueRef.current = inputInitialValue;
- setTimeout(() => {
- setInputRef.current(inputInitialValue);
+ setTimeout(() => {
+ const setInput = setInputRef.current;
+ if (!setInput) return;
+
+ setInput(inputInitialValue);
const textarea = document.querySelector("textarea");
if (textarea) {
textarea.focus();