fix(ai-toolbar): gate paste capture on AI availability
This commit is contained in:
@@ -121,6 +121,7 @@ export const AIToolbar = ({
|
|||||||
"Project sync tomorrow from 9am to 10am on Google Meet with a weekly repeat.",
|
"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.",
|
"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
|
// Ref to imperatively open the file picker from the keyboard shortcut
|
||||||
const imageTriggerRef = useRef<{ open: () => void }>(null);
|
const imageTriggerRef = useRef<{ open: () => void }>(null);
|
||||||
@@ -153,7 +154,7 @@ export const AIToolbar = ({
|
|||||||
// focused element or OS clipboard model (X11/Wayland).
|
// focused element or OS clipboard model (X11/Wayland).
|
||||||
// This is the approach used by Excalidraw's actionPaste.
|
// This is the approach used by Excalidraw's actionPaste.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthenticated || isPending) return;
|
if (!isAuthenticated || isPending || !canUseAi || aiLoading) return;
|
||||||
|
|
||||||
// ── Handler 1: paste event (works when textarea is NOT focused) ───────
|
// ── Handler 1: paste event (works when textarea is NOT focused) ───────
|
||||||
const handleDocumentPaste = (e: ClipboardEvent) => {
|
const handleDocumentPaste = (e: ClipboardEvent) => {
|
||||||
@@ -247,9 +248,9 @@ export const AIToolbar = ({
|
|||||||
document.removeEventListener("paste", handlePasteHandled, {
|
document.removeEventListener("paste", handlePasteHandled, {
|
||||||
capture: true,
|
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) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
@@ -261,7 +262,6 @@ export const AIToolbar = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasImages = imagePreviews.length > 0;
|
const hasImages = imagePreviews.length > 0;
|
||||||
const canUseAi = adminAiEnabled && aiEnabled;
|
|
||||||
const showDisabledState = isAuthenticated && !canUseAi;
|
const showDisabledState = isAuthenticated && !canUseAi;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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 ────────────────────────────────────
|
// ─── Cycle 8: Multi-image thumbnail strip ────────────────────────────────────
|
||||||
//
|
//
|
||||||
// When multiple images are attached, they render as a horizontal scrollable
|
// When multiple images are attached, they render as a horizontal scrollable
|
||||||
|
|||||||
Reference in New Issue
Block a user