Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
const [isComposerModelPickerOpen, setIsComposerModelPickerOpen] = useState(false);
const [isComposerFocused, setIsComposerFocused] = useState(false);
const isMobileViewport = useMediaQuery("max-sm");
const hasCoarsePointer = useMediaQuery({ pointer: "coarse" });
Comment thread
wizzoapp[bot] marked this conversation as resolved.
const isComposerCollapsedMobile =
isMobileViewport && !forceExpandedOnMobile && !isComposerFocused;

Expand Down Expand Up @@ -1800,7 +1801,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
}
if (
key === "Enter" &&
shouldSubmitComposerOnEnter({ isMobileViewport, shiftKey: event.shiftKey })
shouldSubmitComposerOnEnter({ isMobileViewport, hasCoarsePointer, shiftKey: event.shiftKey })
) {
submitComposer();
return true;
Expand Down Expand Up @@ -2006,8 +2007,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
isConnecting ||
isComposerApprovalState ||
pendingUserInputs.length > 0 ||
projectSelectionRequired ||
(environmentUnavailable !== null && activePendingProgress === null)
projectSelectionRequired
) {
return false;
}
Expand Down Expand Up @@ -2542,12 +2542,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
? "Ask for follow-up changes or attach images"
: "Ask anything, @tag files/folders, $use skills, or / for commands"
}
disabled={
isConnecting ||
isComposerApprovalState ||
projectSelectionRequired ||
(environmentUnavailable !== null && activePendingProgress === null)
}
// An unavailable environment must not lock the editor: this fork
// queues sends made while disconnected (`shouldQueueInitialSend`),
// so the user has to be able to type the message that gets queued.
disabled={isConnecting || isComposerApprovalState || projectSelectionRequired}
/>
{showMobilePendingAnswerActions ? (
<div
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/composer-logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ describe("shouldSubmitComposerOnEnter", () => {
it("inserts a newline for Shift+Enter", () => {
expect(shouldSubmitComposerOnEnter({ isMobileViewport: false, shiftKey: true })).toBe(false);
});

it("inserts a newline on a wide touch device, where Enter comes from an on-screen keyboard", () => {
expect(
shouldSubmitComposerOnEnter({
isMobileViewport: false,
hasCoarsePointer: true,
shiftKey: false,
}),
).toBe(false);
});

it("still submits on a wide fine-pointer device", () => {
expect(
shouldSubmitComposerOnEnter({
isMobileViewport: false,
hasCoarsePointer: false,
shiftKey: false,
}),
).toBe(true);
});
});

describe("detectComposerTrigger", () => {
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/composer-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ export interface ComposerTrigger {
rangeEnd: number;
}

// A coarse pointer means an on-screen keyboard, where Enter has to insert a
// newline rather than send — the viewport check alone misses tablets and
// 2-in-1s held in landscape, whose viewport is wider than the mobile breakpoint.
export function shouldSubmitComposerOnEnter(input: {
isMobileViewport: boolean;
shiftKey: boolean;
hasCoarsePointer?: boolean;
}): boolean {
return !input.isMobileViewport && !input.shiftKey;
return !input.isMobileViewport && !input.hasCoarsePointer && !input.shiftKey;
}

const isInlineTokenSegment = (
Expand Down
Loading