Review follow-ups for the insecure-context clipboard fallback:
1. Tag the fallback textarea with data-clipboard-fallback and whitelist
it in PopoutDialog's ANNOTATION_SELECTORS so the transient focus
shift during a fallback copy no longer closes popout dialogs
(TablePopout copy buttons, CodeFilePopout copy contents).
2. Widen AnnotationPanel's onQuickCopy prop to Promise<void | boolean>.
A false resolution now suppresses the Copied flash; void resolution
stays success so existing hosts keep today's behavior. The editor
quick-copy site returns the helper's boolean.
3. In copyTextWithFallback, only flag the copy-event path as success
when clipboardData was present and setData actually ran, and only
when execCommand also reported success. A null clipboardData no
longer calls preventDefault, so the textarea retry still runs.
4. Add tests pinning that the fallback runs synchronously when
navigator.clipboard is absent (execCommand fires before the call
returns, keeping it inside the user-gesture window), that a copy
event without clipboardData is not treated as success, and that the
fallback textarea carries the PopoutDialog focus-out marker and is
removed after the copy resolves.
5. GoalSetupSurface surfaces the real writeText rejection message when
the Clipboard API exists but fails and the fallback also fails; the
generic unavailable message is reserved for the API-absent case.
Closes #1173
Bug: navigator.clipboard only exists in secure browser contexts. Remote-mode Plannotator serves plain HTTP on a non-localhost host, so navigator.clipboard is undefined there and every bare navigator.clipboard.writeText(...) call threw TypeError: Cannot read properties of undefined (reading 'writeText'). Reported from annotate mode on 0.25.1; it affected every copy button. Remote-mode HTTP is the main affected environment.
Fix: New copyTextToClipboard(text): Promise in packages/ui/utils/clipboard.ts. It tries the async Clipboard API (guarded against synchronous throws), falls back to the existing copy-event / execCommand path, returns whether the copy actually happened, and never throws. All 27 bare call sites across packages/ui, packages/editor, and packages/review-editor now go through it, preserving each site's UX (Copied states only on success, existing error toasts and console errors on failure, fire-and-forget sites stay fire-and-forget). GoalSetupSurface gains the fallback and keeps its error message for the all-strategies-failed case. copyTextPreservingFocus keeps its exported signature and behavior unchanged.
Tests: DOM-gated unit tests (packages/ui/utils/clipboard.test.ts, registered in .github/workflows/test.yml) cover: Clipboard API absent (falls back, returns the execCommand result), writeText rejecting, writeText throwing synchronously, writeText resolving (no fallback invoked), everything failing (resolves false, never throws), and the fallback copy event carrying the text payload. Typecheck, full bun test, and the CI DOM test set all pass.