Terminal newlines, OAuth callback, Claude Code settings, and the Files tab #30

Merged
jknapp merged 62 commits from ship/core into main 2026-08-25 18:02:58 +00:00
3 changed files with 40 additions and 5 deletions
Showing only changes of commit 88ffb4744a - Show all commits
+7 -1
View File
@@ -757,7 +757,13 @@
sessionType === 'claude' sessionType === 'claude'
) { ) {
sendTerminalInput('\x1b\r'); sendTerminalInput('\x1b\r');
return false; // xterm must not also send a bare CR, which submits // `preventDefault()` is what stops the submit, not the `return false`.
// xterm's `_keyDown` returns before setting `_keyDownHandled`, so
// `_keyPress` still fires and emits a bare CR for Enter — inserting the
// newline and then submitting the prompt anyway. See the same comment
// in TerminalView.tsx.
e.preventDefault();
return false;
} }
return true; return true;
}); });
@@ -137,19 +137,36 @@ afterEach(() => {
}); });
describe("TerminalView — Shift+Enter", () => { describe("TerminalView — Shift+Enter", () => {
it("sends ESC+CR and nothing else in a Claude session", () => { it("sends ESC+CR and cancels the keydown, so no bare CR follows", () => {
// **The cancel is the load-bearing half, and this test could not see it.**
//
// Returning `false` from xterm's custom key handler does not cancel the
// event: `_keyDown` returns before setting `_keyDownHandled`, so
// `_keyPress` still runs and emits a bare CR for Enter's charCode 13. In a
// real browser that submitted the prompt straight after inserting the
// newline. jsdom never synthesizes the follow-up keypress, so the old
// `expect(sent()).not.toContain("\r")` assertion below could not fail no
// matter what the code did — it was named for a behaviour it could not
// exercise.
//
// Asserting `defaultPrevented` pins the actual mechanism that stops the
// keypress, which is a property jsdom *can* observe.
const { container } = mountSession("claude"); const { container } = mountSession("claude");
fireEvent.keyDown(helperTextarea(container), { const event = new KeyboardEvent("keydown", {
key: "Enter", key: "Enter",
keyCode: 13, keyCode: 13,
shiftKey: true, shiftKey: true,
bubbles: true,
cancelable: true,
}); });
helperTextarea(container).dispatchEvent(event);
// The bytes `/terminal-setup` installs for every other editor. // The bytes `/terminal-setup` installs for every other editor.
expect(sent()).toEqual(["\x1b\r"]); expect(sent()).toEqual(["\x1b\r"]);
// And specifically not the bare CR that would have submitted the prompt.
expect(sent()).not.toContain("\r"); expect(sent()).not.toContain("\r");
// Without this, the browser fires keypress and xterm submits.
expect(event.defaultPrevented).toBe(true);
}); });
it("leaves a plain Enter alone", () => { it("leaves a plain Enter alone", () => {
+13 -1
View File
@@ -414,7 +414,19 @@ export default function TerminalView({ sessionId, active }: Props) {
sessionTypeRef.current === "claude" sessionTypeRef.current === "claude"
) { ) {
sendInput(sessionId, "\x1b\r"); sendInput(sessionId, "\x1b\r");
return false; // xterm must not also send a bare CR, which submits // **`preventDefault()` is what stops the submit, not the `return false`.**
//
// xterm's `_keyDown` returns the instant a custom handler says `false`
// — *before* it sets `_keyDownHandled` and before it cancels the event.
// `_keyPress` then checks that same flag, finds it still false, and
// emits a bare CR for Enter's charCode 13. So returning `false` alone
// sent ESC+CR *and* a submit: the newline was inserted and the
// half-written prompt went to Claude with a stray blank line in it.
// Cancelling the keydown is what stops the browser firing keypress at
// all. Verified in Chromium; jsdom never synthesizes the follow-up
// keypress, which is why the unit test could not see this.
event.preventDefault();
return false;
} }
return true; return true;
}); });