Skip to content

Commit 45bb8fb

Browse files
fix(mobile): Browser menu pastes the phone's clipboard into the page
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 133f57a commit 45bb8fb

3 files changed

Lines changed: 22 additions & 1 deletion

File tree

‎apps/mobile/src/features/browser/BrowserPreviewRouteScreen.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ function BrowserPreviewScreen({
174174
streaming={streaming}
175175
ready={streaming && canControl}
176176
onCommand={(input) => streamRef.current?.command(input)}
177+
onPaste={() => streamRef.current?.paste()}
177178
/>
178179
{live ? (
179180
<PreviewStreamWebView
@@ -209,12 +210,14 @@ function BrowserAddressBar({
209210
ready,
210211
streaming,
211212
onCommand,
213+
onPaste,
212214
}: {
213215
readonly environmentId: EnvironmentId;
214216
readonly tab: PreviewSessionSnapshot;
215217
readonly ready: boolean;
216218
readonly streaming: boolean;
217219
readonly onCommand: PreviewStreamRef["command"];
220+
readonly onPaste: PreviewStreamRef["paste"];
218221
}) {
219222
const url = browserTabUrl(tab);
220223
const [draft, setDraft] = useState<string | null>(null);
@@ -263,7 +266,12 @@ function BrowserAddressBar({
263266
disabled={!ready}
264267
onPress={() => onCommand({ type: "reload" })}
265268
/>
266-
<BrowserTabMenu environmentId={environmentId} tab={tab} disabled={!streaming} />
269+
<BrowserTabMenu
270+
environmentId={environmentId}
271+
tab={tab}
272+
disabled={!streaming}
273+
onPaste={ready ? onPaste : undefined}
274+
/>
267275
</View>
268276
);
269277
}

‎apps/mobile/src/features/browser/BrowserTabMenu.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ export function BrowserTabMenu({
4141
environmentId,
4242
tab,
4343
disabled,
44+
onPaste,
4445
}: {
4546
readonly environmentId: EnvironmentId;
4647
readonly tab: PreviewSessionSnapshot;
4748
readonly disabled: boolean;
49+
readonly onPaste: (() => void) | undefined;
4850
}) {
4951
const adjust = useAtomCommand(previewEnvironment.adjust, "browser tab change");
5052
const resize = useAtomCommand(previewEnvironment.resize, "browser viewport change");
@@ -64,6 +66,7 @@ export function BrowserTabMenu({
6466
: "custom";
6567

6668
const actions: MenuAction[] = [
69+
...(onPaste ? [{ id: "paste", title: "Paste", image: "doc.on.clipboard" }] : []),
6770
{ id: "hard-reload", title: "Hard reload", image: "arrow.clockwise.circle" },
6871
{
6972
id: "appearance",
@@ -112,6 +115,7 @@ export function BrowserTabMenu({
112115
};
113116

114117
const onAction = (id: string) => {
118+
if (id === "paste") return onPaste?.();
115119
if (id === "hard-reload") return void run({ hardReload: true });
116120
if (id === "zoom:in") return void run({ zoomFactor: zoomTo(1) });
117121
if (id === "zoom:out") return void run({ zoomFactor: zoomTo(-1) });

‎apps/mobile/src/features/browser/PreviewStreamWebView.tsx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {
3838

3939
export interface PreviewStreamRef {
4040
readonly command: (input: PreviewStreamInput) => void;
41+
/** Types this device's clipboard into the page's focused field. */
42+
readonly paste: () => void;
4143
readonly togglePictureInPicture: () => void;
4244
}
4345

@@ -268,6 +270,12 @@ function PreviewStreamDocumentView({
268270
webView.current?.injectJavaScript(
269271
`window.T3PreviewStream?.command(${JSON.stringify(input)}); true;`,
270272
);
273+
// The page's own paste would read the environment's clipboard, so the phone's text
274+
// arrives as typing. A tab switch while iOS asks to allow the paste unmounts this view.
275+
const paste = async () => {
276+
const text = await Clipboard.getStringAsync();
277+
if (active.current && text) command({ type: "text", text });
278+
};
271279
const fail = (message: string) => {
272280
if (!active.current || failed.current) return;
273281
failed.current = true;
@@ -296,6 +304,7 @@ function PreviewStreamDocumentView({
296304
);
297305
useImperativeHandle(ref, () => ({
298306
command,
307+
paste: () => void paste().catch(() => Dialog.alert("Could not read the clipboard")),
299308
togglePictureInPicture: () =>
300309
webView.current?.injectJavaScript("window.T3PreviewStream?.pictureInPicture(); true;"),
301310
}));

0 commit comments

Comments
 (0)