diff --git a/web/src/components/paste-box.tsx b/web/src/components/paste-box.tsx new file mode 100644 index 0000000..a279d7a --- /dev/null +++ b/web/src/components/paste-box.tsx @@ -0,0 +1,76 @@ +import { useEffect, useRef } from 'react' + +/** + * Somewhere to paste into, for when the browser will not be asked. + * + * Reading the clipboard from script is a permission, and a phone browser is + * entitled to refuse it — Safari in particular grants it only for a gesture + * it approves of, and answers everything else by doing nothing at all. That + * leaves flue's Paste looking broken for a reason no message can explain, + * because no error is raised: the promise simply never resolves the way it + * was asked to. + * + * Pasting *into a text field* is not a permission. It is the person choosing + * their own clipboard through their own operating system, and no browser has + * ever gated it. So this is the way that cannot be refused: a real, plain, + * ordinary input on the screen, long-pressed like any other, with the + * platform's own Paste in the callout above it. What lands here goes on to + * the terminal. + * + * A fallback and not the front door. When the clipboard can be read, one tap + * is better than three, and this never appears. + */ +export function PasteBox({ onText, onCancel }: { onText: (text: string) => void; onCancel: () => void }) { + const field = useRef(null) + + useEffect(() => { + // Focused on arrival so the callout is one press away rather than two, + // and so the keyboard that was already up does not drop while this opens. + field.current?.focus({ preventScroll: true }) + }, []) + + const send = (text: string) => { + if (text !== '') onText(text) + } + + return ( +
+

+ This browser will not hand over the clipboard. Press and hold here, choose Paste, and it + goes to the terminal. +

+
+