diff --git a/packages/elements/__tests__/reasoning.test.tsx b/packages/elements/__tests__/reasoning.test.tsx
index e874d554..498b9ad0 100644
--- a/packages/elements/__tests__/reasoning.test.tsx
+++ b/packages/elements/__tests__/reasoning.test.tsx
@@ -183,6 +183,47 @@ describe("reasoningTrigger", () => {
expect(screen.getByText("Thought for a few seconds")).toBeInTheDocument();
});
+ it("does not warn when duration is set after streaming ends", () => {
+ const spy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
+ const { rerender } = render(
+
+
+
+ );
+
+ rerender(
+
+
+
+ );
+
+ expect(screen.getByText("Thought for 3 seconds")).toBeInTheDocument();
+ expect(spy).not.toHaveBeenCalled();
+ spy.mockRestore();
+ });
+
+ it("prefers the duration prop over the measured duration", () => {
+ vi.useFakeTimers();
+ const { rerender } = render(
+
+
+
+ );
+
+ act(() => {
+ vi.advanceTimersByTime(2000);
+ });
+
+ rerender(
+
+
+
+ );
+
+ expect(screen.getByText("Thought for 9 seconds")).toBeInTheDocument();
+ vi.useRealTimers();
+ });
+
it("renders custom children", () => {
render(
diff --git a/packages/elements/src/reasoning.tsx b/packages/elements/src/reasoning.tsx
index 4789ee2a..d465c7dd 100644
--- a/packages/elements/src/reasoning.tsx
+++ b/packages/elements/src/reasoning.tsx
@@ -75,10 +75,13 @@ export const Reasoning = memo(
onChange: onOpenChange,
prop: open,
});
- const [duration, setDuration] = useControllableState({
- defaultProp: undefined,
- prop: durationProp,
- });
+ // Kept in plain state: `duration` has no change callback, and
+ // `useControllableState` warns when `durationProp` goes from undefined
+ // (while streaming) to a number (once reasoning finishes).
+ const [measuredDuration, setDuration] = useState(
+ undefined
+ );
+ const duration = durationProp ?? measuredDuration;
const hasEverStreamedRef = useRef(isStreaming);
const [hasAutoClosed, setHasAutoClosed] = useState(false);