fix(ai-toolbar): gate paste capture on AI availability

This commit is contained in:
2026-04-22 17:54:49 -04:00
parent e817fb52cf
commit bfb29d3986
2 changed files with 17 additions and 4 deletions

View File

@@ -121,6 +121,7 @@ export const AIToolbar = ({
"Project sync tomorrow from 9am to 10am on Google Meet with a weekly repeat.",
"Dentist appointment on May 14 at 3pm at Smile Studio, add confirmation #A4821.",
];
const canUseAi = adminAiEnabled && aiEnabled;
// Ref to imperatively open the file picker from the keyboard shortcut
const imageTriggerRef = useRef<{ open: () => void }>(null);
@@ -153,7 +154,7 @@ export const AIToolbar = ({
// focused element or OS clipboard model (X11/Wayland).
// This is the approach used by Excalidraw's actionPaste.
useEffect(() => {
if (!isAuthenticated || isPending) return;
if (!isAuthenticated || isPending || !canUseAi || aiLoading) return;
// ── Handler 1: paste event (works when textarea is NOT focused) ───────
const handleDocumentPaste = (e: ClipboardEvent) => {
@@ -247,9 +248,9 @@ export const AIToolbar = ({
document.removeEventListener("paste", handlePasteHandled, {
capture: true,
});
document.removeEventListener("keydown", handleDocumentKeydown);
document.removeEventListener("keydown", handleDocumentKeydown);
};
}, [isAuthenticated, isPending]); // onImagesSelect intentionally omitted — ref stays current
}, [isAuthenticated, isPending, canUseAi, aiLoading]); // onImagesSelect intentionally omitted — ref stays current
if (isPending) {
return (
@@ -261,7 +262,6 @@ export const AIToolbar = ({
}
const hasImages = imagePreviews.length > 0;
const canUseAi = adminAiEnabled && aiEnabled;
const showDisabledState = isAuthenticated && !canUseAi;
return (

View File

@@ -323,6 +323,19 @@ describe("Keyboard shortcuts toolbar integration contract", () => {
});
});
describe("Global paste capture AI availability gate", () => {
test("document-level paste handlers only arm when AI is actually usable", () => {
const source = readToolbarSource();
expect(source).toContain(
"if (!isAuthenticated || isPending || !canUseAi || aiLoading) return;",
);
expect(source).toContain(
"}, [isAuthenticated, isPending, canUseAi, aiLoading]);",
);
});
});
// ─── Cycle 8: Multi-image thumbnail strip ────────────────────────────────────
//
// When multiple images are attached, they render as a horizontal scrollable