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
4 changes: 3 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ Drafts stay in this browser and re-anchor to changed files on refresh.
dragged to either edge.
- The **Type** button opens an input dock with two modes: **Composer** (edit a
draft with IME, dictation, and images; **Send** adds Enter) and **Direct**
(keys go straight to the terminal).
(keys go straight to the terminal). Its tap raises the keyboard in either
mode; afterwards, sends, scrolling, and shortcut actions leave a dismissed
keyboard closed.
- The `>_` button in Composer offers slash commands for Claude Code, Codex, Pi,
Kimi Code, Grok Build, and Antigravity CLI.
- Install as a PWA for an app-style window; see
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-web-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fileURLToPath, URL } from "node:url";
const publicRoot = fileURLToPath(new URL("../server/public/", import.meta.url));
const maxFileCount = 160;
const maxTotalBytes = 14 * 1024 * 1024;
const maxInitialJsBytes = 802 * 1024;
const maxInitialJsBytes = 803 * 1024;
const maxInitialJsGzipBytes = 256 * 1024;
const maxInitialCssBytes = 196 * 1024;

Expand Down
15 changes: 11 additions & 4 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4090,10 +4090,17 @@ export default function App() {
activateTerminalSurface();
setTerminalComposerOpen(true);
});
// Give the dock focus; Composer's editor opens the keyboard on tap.
document
.querySelector<HTMLElement>(".terminal-composer")
?.focus({ preventScroll: true });
// The tap is the request to type. The dock routes Direct to the
// terminal; in Composer, focus the draft within this gesture so
// phones raise the keyboard in one tap. Later dock focus (sends,
// commands, uploads) stays neutral.
const dock =
document.querySelector<HTMLElement>(".terminal-composer");
dock?.focus({ preventScroll: true });
if (dock?.dataset.inputMode === "composer")
dock
.querySelector<HTMLElement>(".terminal-composer-input")
?.focus({ preventScroll: true });
}}
>
<SquarePen size={20} />
Expand Down
17 changes: 11 additions & 6 deletions web/src/components/TerminalView.inputMode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ async function registerDomTests() {
mobileShortcuts = [[], []];
});
afterEach(async () => {
document.documentElement.classList.remove("keyboard-open");
if (root) await act(async () => root?.unmount());
root = null;
clearTerminalComposerDraft(draftKey);
Expand Down Expand Up @@ -379,7 +380,8 @@ async function registerDomTests() {
}
async function typeGesture() {
// Match App's Type click: open+commit, then synchronously focus the dock
// root, whose handler routes Direct focus, all in one gesture.
// root, whose handler routes Direct focus, and in Composer the draft,
// all in one gesture.
await act(async () => {
flushSync(() => {
open = true;
Expand All @@ -388,6 +390,10 @@ async function registerDomTests() {
const target = container.querySelector<HTMLElement>(".terminal-composer");
expect(target).not.toBeNull();
target!.focus({ preventScroll: true });
if (target!.dataset.inputMode === "composer")
target!
.querySelector<HTMLElement>(".terminal-composer-input")
?.focus({ preventScroll: true });
// Assert during the gesture, before act can flush any later work.
if (
selectedMode() === "direct" &&
Expand Down Expand Up @@ -667,6 +673,9 @@ async function registerDomTests() {
await mount();
if (mode !== "closed") await typeGesture();
if (mode === "direct") await choose("direct");
// Type's tap raised the keyboard in Composer's draft; App marks it.
if (mode === "composer")
document.documentElement.classList.add("keyboard-open");
await frame(mouseReporting);
const term = currentTerminal();
term.modes.mouseTrackingMode = mouseReporting ? "drag" : "none";
Expand Down Expand Up @@ -961,17 +970,13 @@ async function registerDomTests() {
},
);

test("Type keeps Composer neutral until its native draft is explicitly focused", async () => {
test("Type focuses Composer's draft in one tap, and later work stays neutral", async () => {
await mount();
await typeGesture();
expect(selectedMode()).toBe("composer");
expect(document.activeElement).toBe(
container.querySelector(".terminal-composer"),
);
const editor = container.querySelector<HTMLTextAreaElement>(
'[aria-label="Terminal input draft"]',
)!;
act(() => editor.focus());
expect(document.activeElement).toBe(editor);
act(() => editor.blur());
const focused = document.activeElement;
Expand Down
Loading