From f3d74b85759150bffc7a44ac40301d1cb280d66c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 24 Oct 2026 11:44:08 +0700 Subject: [PATCH] fix(plugins): restore deprecated useRevealedText compatibility (#7780) The 0.13 UI entry rename dropped the reveal hook from the 0.12 runtime. Keep it on the legacy entry until that entry is removed, with deprecated SDK typing and migration guidance. Closes #7577 Closes #7722 Co-authored-by: luke Co-authored-by: RoundRedDot Co-authored-by: Matt Cowger <1929548+mcowger@users.noreply.github.com> --- packages/app/src/plugins/evaluate.test.ts | 16 ++++++++++++ packages/app/src/plugins/evaluate.ts | 7 +++++- packages/plugin/src/boundaries.test.ts | 29 +++++++++++++++++----- packages/plugin/src/client/react-native.ts | 3 +++ public-docs/plugins/migration.md | 5 ++-- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/app/src/plugins/evaluate.test.ts b/packages/app/src/plugins/evaluate.test.ts index c1f95b87fa..1aae11f63d 100644 --- a/packages/app/src/plugins/evaluate.test.ts +++ b/packages/app/src/plugins/evaluate.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { useRevealedText } from "@/hooks/use-revealed-text"; import { runPluginClientBundle, type PluginClientRuntime } from "./evaluate"; const runtime = { @@ -705,6 +706,21 @@ describe("evaluatePluginClientBundle", () => { expect(plugin.surfaces.map((surface) => surface.id)).toEqual(["main"]); }); + it("keeps the host reveal hook on the deprecated client/react-native entry", () => { + const plugin = evaluatePluginClientBundle( + "legacy-reveal", + bundle(` + const legacy = require("@getpaseo/plugin/client/react-native"); + const ui = require("@getpaseo/plugin/client/ui"); + if (typeof legacy.useRevealedText !== "function") throw new Error("Missing legacy reveal hook"); + if (legacy.StreamedText !== ui.StreamedText) throw new Error("Missing current UI exports"); + if ("useRevealedText" in ui) throw new Error("Legacy hook leaked into current UI"); + plugin.addScreen({ id: "reveal", title: "Reveal", Component: legacy.useRevealedText }); + `), + ); + expect(plugin.surfaces[0]?.Component).toBe(useRevealedText); + }); + it("keeps shared and client runtime exports separate", () => { expect(() => evaluatePluginClientBundle( diff --git a/packages/app/src/plugins/evaluate.ts b/packages/app/src/plugins/evaluate.ts index 44bcbec57d..ea37209f57 100644 --- a/packages/app/src/plugins/evaluate.ts +++ b/packages/app/src/plugins/evaluate.ts @@ -2,6 +2,7 @@ import { requirePluginText } from "@getpaseo/plugin/client/host"; import type { createPluginHosts } from "./hosts"; import { openExternalUrl } from "@/utils/open-external-url"; import * as pluginUiRuntime from "./react-native/ui"; +import { useRevealedText } from "@/hooks/use-revealed-text"; import { useSettings } from "./settings/use-settings"; import * as pluginSharedRuntime from "@getpaseo/plugin"; import * as pluginClientRuntime from "@getpaseo/plugin/client"; @@ -114,6 +115,10 @@ export type PluginClientRuntime = Pick< | "addHeaderButton" > & { hosts: ReturnType }; +// COMPAT(pluginReactNativeEntrypoint): added in v0.13.0, remove after 2027-04-20 +// The 0.12 entry exposed the reveal hook as well as UI components. +const pluginReactNativeCompatRuntime = { ...pluginUiRuntime, useRevealedText }; + export function runPluginClientBundle( id: string, bundle: string, @@ -503,7 +508,7 @@ export function runPluginClientBundle( }; const runtimeRequire = (name: string): unknown => { // COMPAT(pluginReactNativeEntrypoint): added in v0.13.0, remove after 2027-04-20 - if (name === "@getpaseo/plugin/client/react-native") return pluginUiRuntime; + if (name === "@getpaseo/plugin/client/react-native") return pluginReactNativeCompatRuntime; if (name === "@getpaseo/plugin/client/ui") return pluginUiRuntime; if (name === "react") return React; if (name === "react/jsx-runtime") return ReactJsxRuntime; diff --git a/packages/plugin/src/boundaries.test.ts b/packages/plugin/src/boundaries.test.ts index e2547154bc..c9b54ecdb7 100644 --- a/packages/plugin/src/boundaries.test.ts +++ b/packages/plugin/src/boundaries.test.ts @@ -101,12 +101,29 @@ describe("plugin SDK import boundaries", () => { ); }); - it.each(compatibilityEntries)("%s only re-exports the UI entry", (entry) => { - const source = readFileSync( - path.resolve(sourceDirectory, "..", manifest.exports[entry].source), - "utf8", - ); - expect(source.replace(/\/\/[^\n]*|\/\*[^]*?\*\//g, "").trim()).toBe('export * from "./ui.js";'); + it.each(compatibilityEntries)("%s respects the client boundary", (entry) => { + expect( + boundaryViolations( + path.resolve(sourceDirectory, "..", manifest.exports[entry].source), + "client", + ), + ).toEqual([]); + }); + + it("typechecks a 0.12 reveal-hook consumer without exposing the hook on current UI", () => { + expect( + typeErrors(` + import { useRevealedText, Icon } from "@getpaseo/plugin/client/react-native"; + import { StreamedText } from "@getpaseo/plugin/client/ui"; + // @ts-expect-error the legacy hook is not part of the current UI API + import { useRevealedText as currentHook } from "@getpaseo/plugin/client/ui"; + import { Text } from "react-native"; + function LegacyRow({ text, phase }: { text: string; phase: "streaming" | "complete" }) { + const revealed: string = useRevealedText(text, phase); + return {revealed}; + } + `), + ).toEqual([]); }); it.each(Object.entries(entries))( diff --git a/packages/plugin/src/client/react-native.ts b/packages/plugin/src/client/react-native.ts index e1eb92f17b..13e8f88370 100644 --- a/packages/plugin/src/client/react-native.ts +++ b/packages/plugin/src/client/react-native.ts @@ -1,3 +1,6 @@ // COMPAT(pluginReactNativeEntrypoint): added in v0.13.0, remove after 2027-04-20 /** @deprecated Import Paseo UI from @getpaseo/plugin/client/ui. */ export * from "./ui.js"; + +/** @deprecated Render StreamedText from @getpaseo/plugin/client/ui. Removed after 2027-04-20 with this entry. */ +export declare function useRevealedText(text: string, phase: "streaming" | "complete"): string; diff --git a/public-docs/plugins/migration.md b/public-docs/plugins/migration.md index f430dff92a..fdefda5971 100644 --- a/public-docs/plugins/migration.md +++ b/public-docs/plugins/migration.md @@ -176,8 +176,9 @@ its registration; that registration belongs in the client entry. ### UI imports in v0.13 -Use `/client/ui` for all Paseo UI components and UI hooks. The previous entry remains an -undocumented compatibility alias until 2027-04-20. +Use `/client/ui` for all Paseo UI components and UI hooks. The deprecated +`/client/react-native` entry retains `useRevealedText(text, phase): string` for 0.12 plugins +until removal after 2027-04-20. The hook is deprecated and is not exported from `/client/ui`. For paced timeline text, replace `useRevealedText(text, phase)` and the surrounding `Text` with `` from `/client/ui`.