the vendoring machinery: one command reproduces the whole state

scripts/vendor.sh clones upstream, rewrites @/ imports to relative
paths, and applies scripts/apply-host.py. web/host.ts is ours and gets
placed into the vendored tree, because upstream declined it.

a script rather than a .patch, and that is not a preference. between
cfde698 and 5c83ac6 — one day — upstream changed an import line,
re-indented the component from 6 spaces to 8, and added an editAccess
guard. a context diff fails on all three, none of which concern us. so
every anchor matches structure with flexible whitespace and re-indents
to whatever it finds, and a missing anchor stops the run loudly.

all three prompt-export paths are wired, which is what upstream said a
complete version needs. the other two clipboard writes are ShareMenu's
share-link and ask-an-AI blurb, and the reason they are excluded is
written down so the count does not read as the same oversight twice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 12:04:34 +01:00
co-authored by Claude Fable 5
parent 240d63940d
commit e99b4afbf9
7 changed files with 373 additions and 23 deletions
+5
View File
@@ -42,6 +42,7 @@ import { ButtonRun, CornerIcon, Field, IconBtn, Section, Segmented, SizePresets,
import { AiWriteBtn } from "./AiPanel";
import { popHistory } from "../lib/ai";
import { KIND_TEXT, SWIPE_TEXT, TRANSITION_TEXT, t, useLang } from "../lib/i18n";
import { useHost } from "../lib/host";
/** A text field for a web address: what is typed stays in the box, and only a complete
* http(s) address (or an emptied box) reaches the part. */
@@ -366,6 +367,7 @@ export function FrameInspector({
onSize: (preset: FramePreset) => void;
}) {
const lang = useLang();
const host = useHost();
const [copied, setCopied] = useState(false);
const [saving, setSaving] = useState(false);
const [swipeDir, setSwipeDir] = useState<SwipeDir>("left");
@@ -467,6 +469,9 @@ export function FrameInspector({
copied ? t("copied", lang) : t("prompt", lang),
async () => {
try {
let handled = false;
try { handled = host.onExport?.(prompt, null) === true; } catch {}
if (handled) { setCopied(true); return; }
await navigator.clipboard.writeText(prompt);
setCopied(true);
} catch {}
+5
View File
@@ -6,6 +6,7 @@ import { Doc, Palette, Platform, defaultPlatformOf } from "../lib/tokens";
import { Icon } from "./M3Node";
import { Field, IconBtn, Segmented } from "./ui";
import { t, useLang } from "../lib/i18n";
import { useHost } from "../lib/host";
export function PromptPanel({
doc,
@@ -22,6 +23,7 @@ export function PromptPanel({
const generated = useMemo(() => buildPrompt(doc, widths, undefined, lang), [doc, widths, lang]);
const edited = doc.promptEdit !== undefined;
const text = edited ? doc.promptEdit! : generated;
const host = useHost();
const [copied, setCopied] = useState(false);
useEffect(() => {
@@ -32,6 +34,9 @@ export function PromptPanel({
const copy = async () => {
try {
let handled = false;
try { handled = host.onExport?.(text, null) === true; } catch {}
if (handled) { setCopied(true); return; }
await navigator.clipboard.writeText(text);
setCopied(true);
} catch {}