#3413 · Palette keyboard handling consumes IME confirmation

Bug · Medium priority · Low effort · ui · 2026-09-10

Issue · Base 8ac123f3551e7502a91c93e87329797f34ee626b

Verdict: REPRODUCED · Root-cause confidence: high

TL;DR

The palette treats an IME confirmation key as a command activation key. Its input keydown handler checks the key but never checks composition state. A test on the real component fails on the unchanged main branch in two clean checkouts. A one-line composition guard makes the test and all 55 command subsystem tests pass.

Claims vs findings

ClaimFinding
Composing Enter is consumed by palette handlingVerified twice: defaultPrevented is true, expected false.
Composition guard resolves the behaviorVerified: regression checks unconsumed event, retained combobox, no action, and subsequent normal Enter activation.
Native macOS Pinyin candidate confirmationUnverified: tests use jsdom KeyboardEvent, not an OS candidate window.

Environment

macOS arm64; Node 22.22.3; pnpm 9.15.0; Vitest 4.1.1. Source at the full base commit above. Frozen installs completed separately in both checkouts. The normal pnpm launcher was broken, so commands used npx --yes pnpm@9.15.0. No dependencies were added. No app instance, providers, database, ports, or runtime data were used.

Minimal reproduction

  1. Check out the base commit in a clean get-bb/bb checkout.
  2. Apply the regression-only patch with git apply.
  3. Run:
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run test --filter=@bb/app -- src/components/commands/CommandPalette.test.tsx -t 'keeps composition confirmation'

Expected: confirmation is not prevented; palette stays open and no command runs. Actual on main:

AssertionError: expected true to be false // Object.is equality
expect(confirmation.defaultPrevented).toBe(false);
Test Files  1 failed (1)
Tests  1 failed | 23 skipped (24)

The test stops on the consumed-event assertion. The source path explains selection and closure; the passing fixed run exercises every later assertion.

  it("keeps composition confirmation separate from command activation", async () => {
    renderPalette();
    openPalette();
    await waitFor(() => expect(searchField()).toBeTruthy());

    fireEvent.change(searchField(), { target: { value: ">toggle panel" } });
    await waitFor(() =>
      expect(selectedOption()?.textContent).toContain("Toggle panel"),
    );
    const input = searchField();
    const confirmation = new KeyboardEvent("keydown", {
      key: "Enter",
      isComposing: true,
      bubbles: true,
      cancelable: true,
    });
    fireEvent(input, confirmation);

    expect(confirmation.defaultPrevented).toBe(false);
    expect(screen.queryByRole("combobox")).toBe(input);
    expect(testState.calls).toEqual([]);

    fireEvent.keyDown(input, { key: "Enter" });
    await waitFor(() => expect(testState.calls).toEqual(["panel.toggle"]));
    expect(screen.queryByRole("combobox")).toBeNull();
  });

Root cause

handleKeyDown handles Enter by calling preventDefault and selecting the active command or thread. It has no composition guard. The combobox directly installs that handler. Thus an Enter emitted during composition follows the same selection path as normal activation. This is local UI event policy; no server or protocol change is needed.

Proposed fix

Return immediately when event.nativeEvent.isComposing is true, before handling palette navigation or activation keys. Keep normal Enter behavior. The implementation adds one production line and 27 test lines in two existing command subsystem files.

Verification

The same agent created a second detached temporary Git worktree at the exact base commit, installed with the frozen lockfile, applied only the authored test, and reran the command above. It failed on the same assertion. No correction to the root cause was needed. This is repeated verification by the same agent, not independent review.

After the guard, pnpm exec turbo run test --filter=@bb/app -- src/components/commands passed: 4 files, 55 tests. Native IME/browser-specific event sequencing remains untested.

Related issues and PRs

No linked open PR appeared in the issue timeline metadata at investigation time. No other issue was investigated.

Appendix

First failure · Second clean failure · Passing command suite. Issue content was treated only as untrusted claims; reproduction code was authored from repository evidence.