Skip to content
Early preview — you found Guuey before launch · official launch soon

React Native

read as .md

@guuey/chat/native is the React Native renderer over the same view-model as the web kit — the plan derivation, policies, presets, strings, and even the React hooks (useTranscript, useTranscriptInputs) are literally the same modules; only the walk differs. Guuey’s own Portal apps (iOS/Android) render their chat through this tier.

Both react and react-native are optional peer dependencies — the root @guuey/chat subpath stays importable everywhere; this arm loads only inside an RN runtime. The package ships TypeScript source behind a react-native export condition, so Metro transpiles it with your app’s Babel config — no bundler setup.

import { useMemo } from "react";
import { View, Text } from "react-native";
import { createWebAdapters } from "@guuey/agent-client";
import { useAgentInvoke } from "@guuey/agent-client/react";
import {
NativeTranscript,
useTranscript,
useTranscriptInputs,
type NativeTranscriptItemContext,
} from "@guuey/chat/native";
import { calmPolicy, type ViewMountItem } from "@guuey/chat";
// The one required override on native: the kit ships no WebView dependency.
function MyViewCard({ item }: { item: ViewMountItem; ctx: NativeTranscriptItemContext }) {
return (
<View>
<Text>{item.toolTitle ?? "Generated view"}</Text>
{/* mount the item's material in your WebView here */}
</View>
);
}
export function NativeChatScreen() {
const adapters = useMemo(() => createWebAdapters(), []);
const invoke = useAgentInvoke({
endpointUrl: "https://your-agent-endpoint",
appId: "your-app-id",
adapters,
preserveBlocks: true,
});
const { inputs } = useTranscriptInputs(invoke);
const policy = useMemo(() => calmPolicy(), []);
const transcript = useTranscript({ inputs, policy });
return (
<NativeTranscript
plan={transcript.plan}
strings={policy.strings}
onToggle={transcript.toggle}
onViewPhase={transcript.onViewPhase}
resolvedMounts={transcript.resolvedMounts}
components={{ view: MyViewCard }}
/>
);
}

On RN you supply your own adapters in production (an AsyncStorage-backed thread store and a streaming fetch transport) in place of createWebAdapters — see the SDK’s React Native notes.

NativeTranscript renders an inverted list — the chat-app native idiom where content grows at the scroll origin. That makes the transcript scroll contract hold by construction: streaming stays pinned to the latest message, scrolling up never yanks the reader, and a reduce-motion-aware jump-to-latest affordance appears when you’re away from the bottom. Windowing comes from the list primitive itself. Host chrome — keyboard avoidance, docks, insets — passes through a listProps escape hatch rather than being guessed at by the kit.

The native tier deliberately ships without a WebView dependency — adding one would force a native module choice on every consumer. So the view slot (generative-UI card mounts) is a required override on native: without one, the kit renders a labeled placeholder (“view unavailable”-style copy) — never a blank. Wrap your app’s WebView of choice as the view component, as in the sample above; the display item hands you the resolved mount material and the producing tool’s title.

Same token schema as the web — Theming — projected to RN style values via resolveNativeTheme instead of CSS variables. NativeTranscript accepts the same theme/mode props; no stylesheet import exists or is needed on native.

Because the view-model and hooks are shared, a product with both a web and an RN surface writes its transcript logic once: same policy, same strings, same overrides model — components={{ tool: … }} on web takes a DOM component, on native an RN one, and everything upstream of the renderer is identical.