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.
The shape
Section titled “The shape”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.
The scroll contract, inverted
Section titled “The scroll contract, inverted”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 view mount is a required override
Section titled “The view mount is a required override”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.
Theming
Section titled “Theming”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.
Web and native together
Section titled “Web and native together”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.