diff --git a/frontend/src/components/BoardOnboardingChat.test.tsx b/frontend/src/components/BoardOnboardingChat.test.tsx
new file mode 100644
index 00000000..b32591db
--- /dev/null
+++ b/frontend/src/components/BoardOnboardingChat.test.tsx
@@ -0,0 +1,122 @@
+import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
+import type { ReactNode } from "react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import type { BoardOnboardingRead } from "@/api/generated/model";
+import { BoardOnboardingChat } from "./BoardOnboardingChat";
+
+const startOnboardingMock = vi.fn();
+const getOnboardingMock = vi.fn();
+const answerOnboardingMock = vi.fn();
+const confirmOnboardingMock = vi.fn();
+
+vi.mock("@/hooks/usePageActive", () => ({
+ usePageActive: () => true,
+}));
+
+vi.mock("@/components/ui/dialog", () => ({
+ DialogHeader: ({ children }: { children?: ReactNode }) => (
+
{children}
+ ),
+ DialogFooter: ({ children }: { children?: ReactNode }) => (
+ {children}
+ ),
+ DialogTitle: ({ children }: { children?: ReactNode }) => (
+ {children}
+ ),
+}));
+
+vi.mock("@/api/generated/board-onboarding/board-onboarding", () => ({
+ startOnboardingApiV1BoardsBoardIdOnboardingStartPost: (...args: unknown[]) =>
+ startOnboardingMock(...args),
+ getOnboardingApiV1BoardsBoardIdOnboardingGet: (...args: unknown[]) =>
+ getOnboardingMock(...args),
+ answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost: (...args: unknown[]) =>
+ answerOnboardingMock(...args),
+ confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost: (...args: unknown[]) =>
+ confirmOnboardingMock(...args),
+}));
+
+const buildQuestionSession = (question: string): BoardOnboardingRead => ({
+ id: "session-1",
+ board_id: "board-1",
+ session_key: "session:key",
+ status: "active",
+ messages: [
+ {
+ role: "assistant",
+ content: JSON.stringify({
+ question,
+ options: ["Option A", "Option B"],
+ }),
+ timestamp: "2026-02-15T00:00:00Z",
+ },
+ ],
+ draft_goal: null,
+ created_at: "2026-02-15T00:00:00Z",
+ updated_at: "2026-02-15T00:00:00Z",
+});
+
+describe("BoardOnboardingChat polling", () => {
+ beforeEach(() => {
+ vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
+ startOnboardingMock.mockReset();
+ getOnboardingMock.mockReset();
+ answerOnboardingMock.mockReset();
+ confirmOnboardingMock.mockReset();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("does not keep polling while waiting for user answer on a shown question", async () => {
+ const session = buildQuestionSession("What should we prioritize?");
+ startOnboardingMock.mockResolvedValue({ status: 200, data: session });
+ getOnboardingMock.mockResolvedValue({ status: 200, data: session });
+
+ render(
+ undefined} />,
+ );
+
+ await screen.findByText("What should we prioritize?");
+ const callsBeforeWait = getOnboardingMock.mock.calls.length;
+
+ await act(async () => {
+ vi.advanceTimersByTime(6500);
+ await Promise.resolve();
+ });
+
+ expect(getOnboardingMock.mock.calls.length).toBe(callsBeforeWait);
+ });
+
+ it("continues polling after an answer is submitted and waiting for assistant", async () => {
+ const session = buildQuestionSession("Pick a style");
+ startOnboardingMock.mockResolvedValue({ status: 200, data: session });
+ getOnboardingMock.mockResolvedValue({ status: 200, data: session });
+ answerOnboardingMock.mockResolvedValue({ status: 200, data: session });
+
+ render(
+ undefined} />,
+ );
+
+ await screen.findByText("Pick a style");
+
+ fireEvent.click(screen.getByRole("button", { name: "Option A" }));
+ fireEvent.click(screen.getByRole("button", { name: "Next" }));
+
+ await waitFor(() => {
+ expect(answerOnboardingMock).toHaveBeenCalledTimes(1);
+ });
+
+ const callsBeforePoll = getOnboardingMock.mock.calls.length;
+ await act(async () => {
+ vi.advanceTimersByTime(2500);
+ await Promise.resolve();
+ });
+
+ await waitFor(() => {
+ expect(getOnboardingMock.mock.calls.length).toBeGreaterThan(callsBeforePoll);
+ });
+ });
+});
diff --git a/frontend/src/components/BoardOnboardingChat.tsx b/frontend/src/components/BoardOnboardingChat.tsx
index ac5c95a9..66047dd0 100644
--- a/frontend/src/components/BoardOnboardingChat.tsx
+++ b/frontend/src/components/BoardOnboardingChat.tsx
@@ -247,12 +247,17 @@ export function BoardOnboardingChat({
void startSession();
}, [startSession]);
+ const shouldPollSession =
+ isPageActive && (loading || isAwaitingAgent || (!question && !draft));
+
useEffect(() => {
- if (!isPageActive) return;
+ if (!shouldPollSession) return;
void refreshSession();
- const interval = setInterval(refreshSession, 2000);
+ const interval = setInterval(() => {
+ void refreshSession();
+ }, 2000);
return () => clearInterval(interval);
- }, [isPageActive, refreshSession]);
+ }, [refreshSession, shouldPollSession]);
const handleAnswer = useCallback(
async (value: string, freeText?: string) => {