From d296e665af028926de49c228921827cbe27a585d Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 13 Apr 2026 16:22:03 -0400 Subject: [PATCH 1/4] clean up guide toolbar v1 code --- packages/client/src/clients/guide/client.ts | 158 +----------------- packages/client/src/clients/guide/index.ts | 6 +- packages/client/src/clients/guide/types.ts | 1 - .../client/test/clients/guide/guide.test.ts | 124 -------------- .../guide/context/KnockGuideProvider.tsx | 7 - .../guide/components/Toolbar/V1/V1.tsx | 93 ----------- .../guide/components/Toolbar/V1/index.ts | 1 - .../modules/guide/components/Toolbar/index.ts | 1 - .../src/modules/guide/components/index.ts | 2 +- packages/react/src/modules/guide/index.ts | 1 - .../guide/providers/KnockGuideProvider.tsx | 10 +- .../test/guide/Toolbar/V1/ToolbarV1.test.tsx | 81 --------- 12 files changed, 6 insertions(+), 479 deletions(-) delete mode 100644 packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx delete mode 100644 packages/react/src/modules/guide/components/Toolbar/V1/index.ts delete mode 100644 packages/react/test/guide/Toolbar/V1/ToolbarV1.test.tsx diff --git a/packages/client/src/clients/guide/client.ts b/packages/client/src/clients/guide/client.ts index 162138e8b..9bc230730 100644 --- a/packages/client/src/clients/guide/client.ts +++ b/packages/client/src/clients/guide/client.ts @@ -64,14 +64,6 @@ const DEFAULT_COUNTER_INCREMENT_INTERVAL = 30 * 1000; // in milliseconds // Maximum number of retry attempts for channel subscription const SUBSCRIBE_RETRY_LIMIT = 3; -// Debug query param keys -export const DEBUG_QUERY_PARAMS = { - GUIDE_KEY: "knock_guide_key", - PREVIEW_SESSION_ID: "knock_preview_session_id", -}; - -const DEBUG_STORAGE_KEY = "knock_guide_debug"; - // Return the global window object if defined, so to safely guard against SSR. const checkForWindow = () => { if (typeof window !== "undefined") { @@ -82,76 +74,6 @@ const checkForWindow = () => { export const guidesApiRootPath = (userId: string | undefined | null) => `/v1/users/${userId}/guides`; -// Detect debug params from URL or local storage -const detectDebugParams = (): DebugState => { - const win = checkForWindow(); - if (!win || !win.location) { - return { forcedGuideKey: null, previewSessionId: null }; - } - - const urlParams = new URLSearchParams(win.location.search); - const urlGuideKey = urlParams.get(DEBUG_QUERY_PARAMS.GUIDE_KEY); - const urlPreviewSessionId = urlParams.get( - DEBUG_QUERY_PARAMS.PREVIEW_SESSION_ID, - ); - - // If URL params exist, persist them to localStorage and return them - if (urlGuideKey || urlPreviewSessionId) { - if (win.localStorage) { - try { - const debugState = { - forcedGuideKey: urlGuideKey, - previewSessionId: urlPreviewSessionId, - }; - win.localStorage.setItem(DEBUG_STORAGE_KEY, JSON.stringify(debugState)); - } catch { - // Silently fail in privacy mode - } - } - return { - forcedGuideKey: urlGuideKey, - previewSessionId: urlPreviewSessionId, - }; - } - - // Check local storage if no URL params - let storedGuideKey = null; - let storedPreviewSessionId = null; - - if (win.localStorage) { - try { - const storedDebugState = win.localStorage.getItem(DEBUG_STORAGE_KEY); - if (storedDebugState) { - const parsedDebugState = safeJsonParseDebugParams(storedDebugState); - storedGuideKey = parsedDebugState.forcedGuideKey; - storedPreviewSessionId = parsedDebugState.previewSessionId; - } - } catch { - // Silently fail in privacy mode - } - } - - return { - forcedGuideKey: storedGuideKey, - previewSessionId: storedPreviewSessionId, - }; -}; - -const safeJsonParseDebugParams = (value: string): DebugState => { - try { - const parsed = JSON.parse(value); - return { - forcedGuideKey: parsed?.forcedGuideKey ?? null, - previewSessionId: parsed?.previewSessionId ?? null, - }; - } catch { - return { - forcedGuideKey: null, - previewSessionId: null, - }; - } -}; - type SelectQueryMetadata = { limit: SelectQueryLimit; opts: SelectGuideOpts; @@ -295,15 +217,11 @@ export class KnockGuideClient { ) { const { trackLocationFromWindow = true, - // TODO(KNO-11523): Remove once we ship guide toolbar v2, and offload as - // much debugging specific logic and responsibilities to toolbar. - trackDebugParams = false, throttleCheckInterval = DEFAULT_COUNTER_INCREMENT_INTERVAL, } = options; const win = checkForWindow(); const location = trackLocationFromWindow ? win?.location?.href : undefined; - const debug = trackDebugParams ? detectDebugParams() : undefined; this.store = new Store({ guideGroups: [], @@ -315,7 +233,6 @@ export class KnockGuideClient { location, // Increment to update the state store and trigger re-selection. counter: 0, - debug, }); // In server environments we might not have a socket connection. @@ -560,45 +477,6 @@ export class KnockGuideClient { }); } - exitDebugMode() { - this.knock.log("[Guide] Exiting debug mode"); - - // Clear localStorage debug params - const win = checkForWindow(); - if (win?.localStorage) { - try { - win.localStorage.removeItem(DEBUG_STORAGE_KEY); - } catch { - // Silently fail in privacy mode - } - } - - // Clear debug state from store - this.store.setState((state) => ({ - ...state, - debug: { - forcedGuideKey: null, - previewSessionId: null, - focusedGuideKeys: {}, - }, - previewGuides: {}, // Clear preview guides when exiting debug mode - })); - - // Remove URL query params if present - // Only update the URL if params need to be cleared to avoid unnecessary navigations - if (win?.location) { - const url = new URL(win.location.href); - if ( - url.searchParams.has(DEBUG_QUERY_PARAMS.GUIDE_KEY) || - url.searchParams.has(DEBUG_QUERY_PARAMS.PREVIEW_SESSION_ID) - ) { - url.searchParams.delete(DEBUG_QUERY_PARAMS.GUIDE_KEY); - url.searchParams.delete(DEBUG_QUERY_PARAMS.PREVIEW_SESSION_ID); - win.location.href = url.toString(); - } - } - } - setDebug(debugOpts?: Omit) { this.knock.log("[Guide] .setDebug()"); @@ -1403,43 +1281,9 @@ export class KnockGuideClient { this.knock.log(`[Guide] Detected a location change: ${href}`); - if (!this.options.trackDebugParams) { - this.setLocation(href); - return; - } - - // TODO(KNO-11523): Remove below once we ship toolbar v2. - - // If entering debug mode, fetch all guides. - const currentDebugParams = this.store.state.debug || {}; - const newDebugParams = detectDebugParams(); - - this.setLocation(href, { debug: newDebugParams }); - - // If debug state has changed, refetch guides and resubscribe to the websocket channel - const debugStateChanged = this.checkDebugStateChanged( - currentDebugParams, - newDebugParams, - ); - - if (debugStateChanged) { - this.knock.log( - `[Guide] Debug state changed, refetching guides and resubscribing to the websocket channel`, - ); - this.fetch(); - this.subscribe(); - } + this.setLocation(href); }; - // Returns whether debug params have changed. For guide key, we only check - // presence since the exact value has no impact on fetch/subscribe - private checkDebugStateChanged(a: DebugState, b: DebugState): boolean { - return ( - Boolean(a.forcedGuideKey) !== Boolean(b.forcedGuideKey) || - a.previewSessionId !== b.previewSessionId - ); - } - private listenForLocationChangesFromWindow() { const win = checkForWindow(); if (win?.history && win?.addEventListener) { diff --git a/packages/client/src/clients/guide/index.ts b/packages/client/src/clients/guide/index.ts index 0834dd155..5b2096e4e 100644 --- a/packages/client/src/clients/guide/index.ts +++ b/packages/client/src/clients/guide/index.ts @@ -1,8 +1,4 @@ -export { - KnockGuideClient, - DEBUG_QUERY_PARAMS, - checkActivatable, -} from "./client"; +export { KnockGuideClient, checkActivatable } from "./client"; export { checkStateIfThrottled } from "./helpers"; export type { ToolbarV2RunConfig } from "./helpers"; export type { diff --git a/packages/client/src/clients/guide/types.ts b/packages/client/src/clients/guide/types.ts index 250ed6159..5964b5ef8 100644 --- a/packages/client/src/clients/guide/types.ts +++ b/packages/client/src/clients/guide/types.ts @@ -285,7 +285,6 @@ export type TargetParams = { export type ConstructorOpts = { trackLocationFromWindow?: boolean; - trackDebugParams?: boolean; orderResolutionDuration?: number; throttleCheckInterval?: number; }; diff --git a/packages/client/test/clients/guide/guide.test.ts b/packages/client/test/clients/guide/guide.test.ts index 0c1a3e234..1895324f9 100644 --- a/packages/client/test/clients/guide/guide.test.ts +++ b/packages/client/test/clients/guide/guide.test.ts @@ -166,7 +166,6 @@ describe("KnockGuideClient", () => { queries: {}, location: undefined, counter: 0, - debug: undefined, }); }); @@ -195,7 +194,6 @@ describe("KnockGuideClient", () => { queries: {}, location: "https://example.com", counter: 0, - debug: undefined, }); }); @@ -212,33 +210,7 @@ describe("KnockGuideClient", () => { queries: {}, location: undefined, counter: 0, - debug: undefined, - }); - }); - - test("handles localStorage errors gracefully during initialization", () => { - const mockLocalStorageWithErrors = { - getItem: vi.fn().mockImplementation(() => { - throw new Error("Privacy mode or quota exceeded"); - }), - setItem: vi.fn().mockImplementation(() => { - throw new Error("Privacy mode or quota exceeded"); - }), - }; - - vi.stubGlobal("window", { - location: { - search: - "?knock_guide_key=test_guide&knock_preview_session_id=session123", - }, - localStorage: mockLocalStorageWithErrors, }); - - expect(() => { - new KnockGuideClient(mockKnock, channelId, {}, { trackDebugParams: true }); - }).not.toThrow(); - - expect(mockLocalStorageWithErrors.setItem).toHaveBeenCalled(); }); test("starts the counter interval clock and sets the interval id", () => { @@ -3714,102 +3686,6 @@ describe("KnockGuideClient", () => { expect(windowWithHistory.history.replaceState).toBe(originalReplaceState); }); - test("handleLocationChange calls subscribe when entering debug mode", () => { - const mockLocalStorage = { - getItem: vi.fn(), - setItem: vi.fn(), - }; - - vi.stubGlobal("window", { - ...mockWindow, - location: { - href: "https://example.com/dashboard?knock_guide_key=test_guide", - search: "?knock_guide_key=test_guide", - }, - localStorage: mockLocalStorage, - }); - - const client = new KnockGuideClient( - mockKnock, - channelId, - {}, - { trackLocationFromWindow: true, trackDebugParams: true }, - ); - - client.store.state.debug = { forcedGuideKey: null }; - client.store.state.location = "https://example.com/dashboard"; - - const subscribeSpy = vi - .spyOn(client, "subscribe") - .mockImplementation(() => {}); - - const fetchSpy = vi - .spyOn(client, "fetch") - .mockImplementation(() => Promise.resolve({ status: "ok" })); - - client["handleLocationChange"](); - - expect(fetchSpy).toHaveBeenCalled(); - expect(subscribeSpy).toHaveBeenCalled(); - - // Should persist debug parameters to localStorage when detected in URL - expect(mockLocalStorage.setItem).toHaveBeenCalledWith( - "knock_guide_debug", - JSON.stringify({ - forcedGuideKey: "test_guide", - previewSessionId: null, - }), - ); - }); - - test("handleLocationChange calls subscribe when exiting debug mode", () => { - const mockLocalStorage = { - getItem: vi.fn().mockImplementation((key: string) => { - // Simulate localStorage having stored debug values from previous session - if (key === "knock_guide_debug") { - return JSON.stringify({ - forcedGuideKey: "stored_guide", - previewSessionId: "stored_session", - }); - } - return null; - }), - setItem: vi.fn(), - }; - - vi.stubGlobal("window", { - ...mockWindow, - location: { - href: "https://example.com/dashboard", - search: "", - }, - localStorage: mockLocalStorage, - }); - - const client = new KnockGuideClient( - mockKnock, - channelId, - {}, - { trackLocationFromWindow: true, trackDebugParams: true }, - ); - - client.store.state.debug = { forcedGuideKey: "test_guide" }; - client.store.state.location = - "https://example.com/dashboard?knock_guide_key=test_guide"; - - const subscribeSpy = vi - .spyOn(client, "subscribe") - .mockImplementation(() => {}); - - client["handleLocationChange"](); - - expect(subscribeSpy).toHaveBeenCalled(); - - // Should read from localStorage when no URL parameters are present - expect(mockLocalStorage.getItem).toHaveBeenCalledWith( - "knock_guide_debug", - ); - }); }); describe("private methods", () => { diff --git a/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx b/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx index 17594e70e..eef983345 100644 --- a/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx +++ b/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx @@ -23,7 +23,6 @@ export type KnockGuideProviderProps = { colorMode?: ColorMode; targetParams?: KnockGuideTargetParams; trackLocationFromWindow?: boolean; - trackDebugParams?: boolean; orderResolutionDuration?: number; // in milliseconds throttleCheckInterval?: number; // in milliseconds }; @@ -37,10 +36,6 @@ export const KnockGuideProvider: React.FC< colorMode = "light", targetParams = {}, trackLocationFromWindow = true, - // Whether the guide client should look for debug params in url/local storage - // to launch guide toolbar. Set to true if using toolbar v1. - // TODO(KNO-11523): Remove this once we ship v2. - trackDebugParams = false, // Default to 0 which works well for react apps as this "yields" to react for // one render cyle first and close the group stage. orderResolutionDuration = 0, @@ -60,7 +55,6 @@ export const KnockGuideProvider: React.FC< const knockGuideClient = React.useMemo(() => { return new KnockGuideClient(knock, channelId, stableTargetParams, { trackLocationFromWindow, - trackDebugParams, orderResolutionDuration, throttleCheckInterval, }); @@ -69,7 +63,6 @@ export const KnockGuideProvider: React.FC< channelId, stableTargetParams, trackLocationFromWindow, - trackDebugParams, orderResolutionDuration, throttleCheckInterval, ]); diff --git a/packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx b/packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx deleted file mode 100644 index 1f47d17d6..000000000 --- a/packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useGuideContext, useStore } from "@knocklabs/react-core"; -import { Button } from "@telegraph/button"; -import { Stack } from "@telegraph/layout"; -import { Tag } from "@telegraph/tag"; -import { Text } from "@telegraph/typography"; -import { Minimize2, Undo2, Wrench } from "lucide-react"; -import { useState } from "react"; - -import { KnockButton } from "../KnockButton"; -import { TOOLBAR_Z_INDEX } from "../shared"; -import "../styles.css"; - -export const V1 = () => { - const [isCollapsed, setIsCollapsed] = useState(false); - - const { client } = useGuideContext(); - const debugState = useStore(client.store, (state) => state.debug); - - if (!debugState?.forcedGuideKey) { - return null; - } - - const handleExit = () => { - client.exitDebugMode(); - }; - - const handleToggleCollapse = () => { - setIsCollapsed(!isCollapsed); - }; - - if (isCollapsed) { - return ; - } - - return ( - - - - Debug - - - - {debugState.forcedGuideKey} - - - - -