authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-20 20:34:06-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-20 20:34:06-03:00
logc789cd174ae50f50ecb0a34b96a81e00b0f5c823
tree72fdf10f5c0a22adf5d53b325714fda10d0a662a
parent67285e4b8719000eecb9de2a424609ca5c6f0274
signature Signed by PGP key B5690EEEBB952194

fix(web): skip image inline chip when composer is empty (#12528)


1 files changed, 20 insertions(+), 2 deletions(-)

apps/web/src/components/chat/ChatComposer.tsx+20-2
......@@ -206,6 +206,7 @@ import {
206206import { useOpenPrLink } from "~/lib/openPullRequestLink";
207207import {
208208 collectInlineContextIds,
209 stripInlineContextReferences,
209210 type ComposerContextReference,
210211 ensureInlineContextReferences,
211212 formatInlineContextReference,
......@@ -5241,6 +5242,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
52415242 options?: {
52425243 readonly source?: ChatFileAttachment["source"];
52435244 readonly selection?: { start: number; end: number };
5245 readonly skipImageInlineChip?: boolean;
52445246 },
52455247 ): Promise<boolean> => {
52465248 if (!activeThreadId || files.length === 0 || isRevertingCheckpointRef.current) return false;
......@@ -5260,6 +5262,20 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
52605262 // large image is being compressed, and the attachments and errors belong
52615263 // to the thread the paste happened in.
52625264 const threadId = activeThreadId;
5265 // Images landing with no prose live on the shelf with no chip. Read before
5266 // the awaits below: compression is async and the prompt may change while it
5267 // runs. An explicit selection replace and states where the editor refuses
5268 // input (connecting, approval, pending questions, project selection) still
5269 // get chips so the image is never invisible, unless paste-as-text explicitly
5270 // requests no inline image chip.
5271 const imageAttachmentsGetChips =
5272 !options?.skipImageInlineChip &&
5273 (options?.selection !== undefined ||
5274 isConnecting ||
5275 isComposerApprovalState ||
5276 pendingUserInputs.length > 0 ||
5277 projectSelectionRequired ||
5278 stripInlineContextReferences(promptRef.current).trim().length > 0);
52635279
52645280 // Validation happens synchronously so concurrent pastes see each other:
52655281 // accepted files reserve their attachment slots (via the pending counter)
......@@ -5416,7 +5432,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
54165432 : [],
54175433 );
54185434 const storedImages = nextImages.filter((image) => storedImageIds.has(image.id));
5419 if (storedImages.length > 0) {
5435 if (storedImages.length > 0 && imageAttachmentsGetChips) {
54205436 insertedAny =
54215437 insertAttachmentReferences(storedImages.map(imageContextReference)) || insertedAny;
54225438 }
......@@ -5444,6 +5460,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
54445460 /**
54455461 * Chips for freshly attached files land at the caret; when the editor cannot take
54465462 * input (approval, pending questions) they are appended so the file is never invisible.
5463 * Images skip this when they land with no prose and the editor takes input:
5464 * the shelf thumbnail is enough.
54475465 */
54485466 const insertAttachmentReferences = (
54495467 references: ReadonlyArray<ComposerContextReference>,
......@@ -5582,7 +5600,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
55825600 ) {
55835601 event.preventDefault();
55845602 event.stopPropagation();
5585 void addComposerAttachments(files);
5603 void addComposerAttachments(files, { skipImageInlineChip: bypassAutoAttachment });
55865604 return;
55875605 }
55885606