diff --git a/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx b/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx
index 4863aa05..da60e5b2 100644
--- a/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx
+++ b/src/Web/autopilot-monitor-web/components/WhatsNewPanelHost.tsx
@@ -1,12 +1,13 @@
"use client";
-import { useEffect } from "react";
-import { usePathname } from "next/navigation";
+import { Suspense, useEffect, useRef } from "react";
+import { usePathname, useSearchParams } from "next/navigation";
import { WhatsNewPanel } from "./WhatsNewPanel";
import { useWhatsNewState, openWhatsNew } from "@/lib/whatsNewStore";
import { trackEvent } from "@/lib/appInsights";
import { isPublicPath } from "@/lib/hostRouting";
import { WHATS_NEW_CHANNELS, type WhatsNewChannel } from "@/lib/whatsNew";
+import { useAuth } from "@/contexts/AuthContext";
/**
* Mounts the What's new panel once, in the root layout, so the portal navbar, the landing
@@ -24,16 +25,43 @@ export function WhatsNewPanelHost() {
const pathname = usePathname();
const onPublicPage = isPublicPath(pathname);
+ return (
+ <>
+ {/* useSearchParams() needs a Suspense boundary for the static prerender. */}
+
+
+
+ {panelOpen && }
+ >
+ );
+}
+
+/**
+ * Opens the panel once each time `?whats-new=` arrives, not only on the first mount: a portal
+ * link opened without a session goes through the sign-in, which lands on the origin root and
+ * reaches the link again by a client-side navigation (AuthGate), where this layout stays
+ * mounted. A protected page waits for the session, so the panel neither flashes before the
+ * sign-in redirect nor counts the open twice.
+ */
+function WhatsNewDeepLink({ onPublicPage }: { onPublicPage: boolean }) {
+ const requested = useSearchParams()?.get("whats-new") ?? null;
+ const { isAuthenticated, isLoading } = useAuth();
+ const ready = onPublicPage || (isAuthenticated && !isLoading);
+ const handled = useRef(false);
+
useEffect(() => {
- const requested = new URLSearchParams(window.location.search).get("whats-new");
- if (requested === null) return;
+ if (requested === null) {
+ handled.current = false;
+ return;
+ }
+ if (handled.current || !ready) return;
+ handled.current = true;
const channel = (WHATS_NEW_CHANNELS as readonly string[]).includes(requested)
? (requested as WhatsNewChannel)
: undefined;
trackEvent("whats_new_opened", { source: "deeplink", channel: channel ?? "default" });
openWhatsNew("deeplink", channel);
- }, []);
+ }, [requested, ready]);
- if (!panelOpen) return null;
- return ;
+ return null;
}