From 86d1ddbfb505c9186ef3e4a70ce06a4e94b13f7f Mon Sep 17 00:00:00 2001 From: Levi Rivkin Date: Mon, 13 Jul 2026 20:12:30 +0000 Subject: [PATCH] Forward teaser.dismissible through FloatBubble/useFloatBubble MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1.15.0 teaser `dismissible` option was added to the core SDK but the React wrapper never forwarded it. useFloatBubble stabilizes the teaser prop on its primitive fields and only carried `enabled`, `delay`, and `sound`, so `dismissible` was silently dropped — `teaser={{ dismissible: false }}` had no effect and the × dismiss button always rendered. Add `dismissible` to the stabilized teaser object (and its useMemo deps) so it reaches createFloatBubble, plus a test asserting all teaser fields are forwarded. Also audited the other teaser surfaces to confirm none share the bug: the vanilla createFloatBubble config and the CDN data-perspective-teaser* attributes both forward dismissible already (core resolveTeaserConfig spreads all fields; the data-attribute parser reads all four), each covered by existing tests. Corrected the EmbedConfig.teaser JSDoc, which listed only enabled/delay/sound, to include dismissible. Adds a changeset patching both packages. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_0172vANJgSbNmv6nnQUErgU2 --- .changeset/float-teaser-dismissible-react.md | 8 ++++++ .../src/hooks/useFloatBubble.test.ts | 28 +++++++++++++++++++ .../sdk-react/src/hooks/useFloatBubble.ts | 10 +++++-- packages/sdk/src/types.ts | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 .changeset/float-teaser-dismissible-react.md diff --git a/.changeset/float-teaser-dismissible-react.md b/.changeset/float-teaser-dismissible-react.md new file mode 100644 index 0000000..fb92032 --- /dev/null +++ b/.changeset/float-teaser-dismissible-react.md @@ -0,0 +1,8 @@ +--- +"@perspective-ai/sdk-react": patch +"@perspective-ai/sdk": patch +--- + +Forward the `teaser.dismissible` option through `FloatBubble` / `useFloatBubble` to the core SDK. The hook that stabilizes the teaser prop only carried `enabled`, `delay`, and `sound`, so it silently dropped `dismissible` — setting `teaser={{ dismissible: false }}` had no effect when using the React package (the × dismiss button still rendered). It is now forwarded alongside the other teaser fields. + +Also corrects the `EmbedConfig.teaser` JSDoc to list `dismissible` among the fields it controls. The vanilla config, CDN `data-perspective-teaser-dismissible` attribute, and core resolution already handled `dismissible` correctly; only the React wrapper was dropping it. diff --git a/packages/sdk-react/src/hooks/useFloatBubble.test.ts b/packages/sdk-react/src/hooks/useFloatBubble.test.ts index 019207a..71ead81 100644 --- a/packages/sdk-react/src/hooks/useFloatBubble.test.ts +++ b/packages/sdk-react/src/hooks/useFloatBubble.test.ts @@ -64,6 +64,34 @@ describe("useFloatBubble", () => { expect(mockUnmount).toHaveBeenCalledTimes(1); }); + it("forwards all teaser fields to createFloatBubble", async () => { + const { unmount } = renderHook(() => + useFloatBubble({ + researchId: "test-research-id", + teaser: { + enabled: true, + delay: 2000, + sound: false, + dismissible: false, + }, + }) + ); + await act(async () => {}); + + expect(mockCreateFloatBubble).toHaveBeenCalledWith( + expect.objectContaining({ + teaser: { + enabled: true, + delay: 2000, + sound: false, + dismissible: false, + }, + }) + ); + + unmount(); + }); + it("returns expected interface", async () => { const { result, unmount } = renderHook(() => useFloatBubble({ researchId: "test-research-id" }) diff --git a/packages/sdk-react/src/hooks/useFloatBubble.ts b/packages/sdk-react/src/hooks/useFloatBubble.ts index dd76bbd..4247f46 100644 --- a/packages/sdk-react/src/hooks/useFloatBubble.ts +++ b/packages/sdk-react/src/hooks/useFloatBubble.ts @@ -115,13 +115,19 @@ export function useFloatBubble( const teaserEnabled = teaser?.enabled; const teaserDelay = teaser?.delay; const teaserSound = teaser?.sound; + const teaserDismissible = teaser?.dismissible; const hasTeaser = teaser !== undefined; const stableTeaser = useMemo( (): EmbedConfig["teaser"] => hasTeaser - ? { enabled: teaserEnabled, delay: teaserDelay, sound: teaserSound } + ? { + enabled: teaserEnabled, + delay: teaserDelay, + sound: teaserSound, + dismissible: teaserDismissible, + } : undefined, - [hasTeaser, teaserEnabled, teaserDelay, teaserSound] + [hasTeaser, teaserEnabled, teaserDelay, teaserSound, teaserDismissible] ); // Resolve ReactNode icons to SVG strings for the core SDK diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 4121cf9..8884667 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -149,7 +149,7 @@ export interface EmbedConfig { channel?: AIAssistantChannel | AIAssistantChannel[] | null; /** Welcome message shown as a teaser bubble next to the float button. Only used for float-type embeds. */ welcomeMessage?: string; - /** Controls if and when the welcome teaser appears (`enabled`, `delay`, `sound`). Only used for float-type embeds. */ + /** Controls if and when the welcome teaser appears (`enabled`, `delay`, `sound`, `dismissible`). Only used for float-type embeds. */ teaser?: TeaserConfig; /** Custom button text for popup/slider triggers */ buttonText?: string;