diff --git a/gui/src/components/OnboardingCard/hooks/useOnboardingCard.test.tsx b/gui/src/components/OnboardingCard/hooks/useOnboardingCard.test.tsx
new file mode 100644
index 00000000000..456599b4733
--- /dev/null
+++ b/gui/src/components/OnboardingCard/hooks/useOnboardingCard.test.tsx
@@ -0,0 +1,74 @@
+import { screen } from "@testing-library/react";
+import { beforeEach, describe, expect, it } from "vitest";
+import { getLocalStorage, setLocalStorage } from "../../../util/localStorage";
+import { renderWithProviders } from "../../../util/test/render";
+import { useOnboardingCard } from "./useOnboardingCard";
+
+/**
+ * Minimal probe that surfaces the hook's state and actions to the DOM, so the
+ * hook can be exercised through the same providers the real card renders in.
+ */
+function OnboardingCardProbe() {
+ const { show, open, close } = useOnboardingCard();
+
+ return (
+
+ {String(show)}
+
+
+
+ );
+}
+
+describe("useOnboardingCard", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it("shows the card by default for a user who has not onboarded", async () => {
+ await renderWithProviders();
+
+ expect(screen.getByTestId("show").textContent).toBe("true");
+ });
+
+ it("persists the dismissal when the card is closed", async () => {
+ const { user } = await renderWithProviders();
+
+ await user.click(screen.getByTestId("close"));
+
+ expect(screen.getByTestId("show").textContent).toBe("false");
+ expect(getLocalStorage("hasDismissedOnboardingCard")).toBe(true);
+ });
+
+ // Regression test for https://github.com/continuedev/continue/issues/12582
+ it("clears the persisted dismissal when the card is reopened", async () => {
+ setLocalStorage("hasDismissedOnboardingCard", true);
+
+ const { user } = await renderWithProviders();
+ expect(screen.getByTestId("show").textContent).toBe("false");
+
+ await user.click(screen.getByTestId("open"));
+
+ expect(screen.getByTestId("show").textContent).toBe("true");
+ expect(getLocalStorage("hasDismissedOnboardingCard")).toBe(false);
+ });
+
+ // The redux `show` flag is not persisted by redux-persist, so reopening has
+ // to clear the localStorage flag or the card vanishes again on next load.
+ it("keeps the card visible after a reload once it has been reopened", async () => {
+ setLocalStorage("hasDismissedOnboardingCard", true);
+
+ const first = await renderWithProviders();
+ await first.user.click(screen.getByTestId("open"));
+ first.unmount();
+
+ // A fresh store stands in for a webview reload: only localStorage carries over.
+ await renderWithProviders();
+
+ expect(screen.getByTestId("show").textContent).toBe("true");
+ });
+});
diff --git a/gui/src/components/OnboardingCard/hooks/useOnboardingCard.ts b/gui/src/components/OnboardingCard/hooks/useOnboardingCard.ts
index 75bc28adcfa..253839e83ab 100644
--- a/gui/src/components/OnboardingCard/hooks/useOnboardingCard.ts
+++ b/gui/src/components/OnboardingCard/hooks/useOnboardingCard.ts
@@ -38,6 +38,9 @@ export function useOnboardingCard(): UseOnboardingCard {
}
async function open(tab?: OnboardingModes) {
+ // Clear the dismissal flag set by `close`. The redux `show` flag is not
+ // persisted, so without this the card disappears again on the next reload.
+ setLocalStorage("hasDismissedOnboardingCard", false);
navigate("/");
dispatch(
setOnboardingCard({
diff --git a/gui/src/pages/config/sections/HelpSection.tsx b/gui/src/pages/config/sections/HelpSection.tsx
index 1d1b2febc0f..1d16cafe285 100644
--- a/gui/src/pages/config/sections/HelpSection.tsx
+++ b/gui/src/pages/config/sections/HelpSection.tsx
@@ -8,10 +8,10 @@ import {
import { useContext, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import Shortcut from "../../../components/gui/Shortcut";
+import { useOnboardingCard } from "../../../components/OnboardingCard/hooks/useOnboardingCard";
import { Card } from "../../../components/ui";
import { IdeMessengerContext } from "../../../context/IdeMessenger";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
-import { setOnboardingCard } from "../../../redux/slices/uiSlice";
import { saveCurrentSession } from "../../../redux/thunks/session";
import { isJetBrains } from "../../../util";
import { ROUTES } from "../../../util/navigation";
@@ -150,6 +150,7 @@ export function HelpSection() {
const ideMessenger = useContext(IdeMessengerContext);
const navigate = useNavigate();
const dispatch = useAppDispatch();
+ const onboardingCard = useOnboardingCard();
const currentSession = useAppSelector((state) => state.session);
@@ -226,12 +227,7 @@ export function HelpSection() {
generateTitle: true,
}),
);
- dispatch(
- setOnboardingCard({
- show: true,
- activeTab: undefined,
- }),
- );
+ void onboardingCard.open();
ideMessenger.post("showTutorial", undefined);
}}
/>