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 <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: Matt Cowger <1929548+mcowger@users.noreply.github.com>
This commit is contained in:
Mohamed Boudra
2026-10-24 11:44:08 +07:00
committed by GitHub
co-authored by luke RoundRedDot Matt Cowger
parent 686588c44b
commit f3d74b8575
5 changed files with 51 additions and 9 deletions
+16
View File
@@ -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(
+6 -1
View File
@@ -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<typeof createPluginHosts> };
// 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;
+23 -6
View File
@@ -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 <Text>{revealed}</Text>;
}
`),
).toEqual([]);
});
it.each(Object.entries(entries))(
@@ -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;
+3 -2
View File
@@ -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
`<StreamedText text={text} phase={phase} style={style} />` from `/client/ui`.