fix: keep Enter as newline in mobile chat composer

This commit is contained in:
Andrey Romantsev
2026-06-25 18:11:41 +02:00
parent 790b36f0ea
commit 32ea809adc
4 changed files with 40 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Keep Enter/Return in the mobile chat composer for new lines, and send messages there only from the send button.
+7 -2
View File
@@ -13,6 +13,7 @@ import { machineSessionKey } from "../machineKeys";
import { detectPromptCompletionTrigger, fileCompletionInsertText, type PromptCompletionTrigger } from "../promptCompletions";
import { clearDraft, loadDraft, saveDraft } from "../promptDraftStorage";
import { loadAttachmentDelivery, saveAttachmentDelivery } from "../attachmentPreferences";
import { createMobilePromptEnterMedia, shouldSendPromptOnEnter } from "../promptEnterBehavior";
import { promptEditorStyles, type CompletionItem } from "./shared";
import { renderAttachIcon, renderSendIcon, renderQueueIcon, renderSteerIcon, renderStopIcon, renderThinkingGauge } from "./promptEditorIcons";
import { thinkingGauge, thinkingLevelLabel } from "../../../shared/thinkingLevels";
@@ -59,6 +60,7 @@ export class PromptEditor extends LitElement {
private editor: EditorView | undefined;
private readonly editableCompartment = new Compartment();
private readonly readOnlyCompartment = new Compartment();
private readonly mobilePromptEnterMedia = createMobilePromptEnterMedia();
protected override willUpdate(changed: PropertyValues<this>) {
if (!changed.has("sessionId") && !changed.has("machineId")) return;
@@ -238,7 +240,7 @@ export class PromptEditor extends LitElement {
{ key: "ArrowDown", run: () => this.moveCompletion(1) },
{ key: "ArrowUp", run: () => this.moveCompletion(-1) },
{ key: "Escape", run: () => this.closeCompletions() },
{ key: "Enter", run: () => this.handleEditorEnter() },
{ key: "Enter", run: (view) => this.handleEditorEnter(view) },
{ key: "Shift-Enter", run: (view) => insertNewlineContinueMarkup(view) || insertNewlineAndIndent(view) },
{ key: "Tab", run: (view) => this.handleEditorTab(view) },
{ key: "Shift-Tab", run: (view) => indentWithTab.shift?.(view) ?? false },
@@ -335,12 +337,15 @@ export class PromptEditor extends LitElement {
return true;
}
private handleEditorEnter(): boolean {
private handleEditorEnter(view: EditorView): boolean {
if (this.completions.length) {
const completion = this.completions[this.selectedIndex];
if (completion !== undefined) this.pick(completion);
return true;
}
if (!shouldSendPromptOnEnter(this.mobilePromptEnterMedia)) {
return insertNewlineContinueMarkup(view) || insertNewlineAndIndent(view);
}
this.send(this.canSteer || this.isCompacting ? "followUp" : undefined);
return true;
}
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { MOBILE_PROMPT_ENTER_MEDIA_QUERY, shouldSendPromptOnEnter, type PromptEnterMedia } from "./promptEnterBehavior";
describe("promptEnterBehavior", () => {
it("uses the expected mobile media query", () => {
expect(MOBILE_PROMPT_ENTER_MEDIA_QUERY).toBe("(pointer: coarse), (max-width: 760px)");
});
it("sends on Enter outside the mobile environment", () => {
expect(shouldSendPromptOnEnter({ matches: false } satisfies PromptEnterMedia)).toBe(true);
expect(shouldSendPromptOnEnter(undefined)).toBe(true);
});
it("keeps Enter as a newline in the mobile environment", () => {
expect(shouldSendPromptOnEnter({ matches: true } satisfies PromptEnterMedia)).toBe(false);
});
});
+11
View File
@@ -0,0 +1,11 @@
export const MOBILE_PROMPT_ENTER_MEDIA_QUERY = "(pointer: coarse), (max-width: 760px)";
export type PromptEnterMedia = Pick<MediaQueryList, "matches">;
export function createMobilePromptEnterMedia(): PromptEnterMedia | undefined {
return typeof window !== "undefined" && "matchMedia" in window ? window.matchMedia(MOBILE_PROMPT_ENTER_MEDIA_QUERY) : undefined;
}
export function shouldSendPromptOnEnter(media = createMobilePromptEnterMedia()): boolean {
return media?.matches !== true;
}