← reports

#3124 · Mobile composer sent on the first tested touch

Bug Medium Effort: Low ui mobile open on GitHub 2026-09-05 · base ae6c0769c

Verdict: NOT REPRODUCED · Root-cause confidence: low

1. TL;DR

The report describes an expanded phone composer shrinking before its first send tap can commit. Two clean runs of current canonical main did the opposite: the pointer press was canceled to retain editor focus, the form submitted once, the editor blurred afterward, and the fake provider returned an echo after that one touch. A focused regression probe also observed submit before blur in both checkouts, while all 148 existing tests in the two relevant suites passed. The original TestFlight build, phone model, and iOS version were not supplied, and this host could not automate a signed TestFlight WKWebView, so a device-specific failure remains possible.

2. Claims vs findings

ClaimStatusEvidence
The first send touch is consumed by a collapse or focus transition.Not reproducedBoth mobile-width touch runs recorded pointerdown as default-prevented while the editor remained focused, followed by click, submit, and only then blur.
A second touch is required to send.Not reproducedEach run used one touch; the draft cleared and the fake provider rendered exactly one response containing the probe text.
The composer collapses when focus and the software keyboard leave after a pointer submission.Verified in focused testsThe existing follow-up test confirms collapse after the keyboard viewport settles, while the new probe confirms the submit callback runs before the blur that starts that process.
The behavior occurs in the BB TestFlight WKWebView on an iPhone.UnverifiedNo TestFlight build number, phone model, or iOS version was available. The direct runs used Chrome touch emulation with an iPhone-sized viewport and Safari user agent, not a signed TestFlight WKWebView.

3. Environment

4. Minimal reproduction

  1. Check out the trusted base commit, then run pnpm install --frozen-lockfile --prefer-offline and pnpm exec turbo run build.
  2. Start the isolated harness with BB_MOBILE_E2E_SERVE_APP=1 BB_MOBILE_E2E_PORT=53124 pnpm exec turbo run e2e:mobile-backend --filter=@bb/integration-tests.
  3. Open the seeded idle thread at a 393×852 touch viewport, focus the follow-up editor, enter a unique probe, and invoke one touch on [data-promptbox-submit-action].
  4. Record the pointer, click, form-submit, and blur events; wait for the fake provider's response. Repeat from a second clean checkout on a new port.
  5. Insert this focused probe beside the existing compact-layout tests and run pnpm exec turbo run test --filter=@bb/app -- src/components/promptbox/PromptBoxInternal.test.tsx -t "commits a coarse-pointer submission before the editor blurs":
it("commits a coarse-pointer submission before the editor blurs", () => {
  const restoreMatchMedia = mockPointerCoarse(true);
  try {
    const sequence: string[] = [];
    const onSubmit = vi.fn(() => sequence.push("submit"));
    render(
      <PromptBoxInternal
        {...createPromptBoxProps({
          value: "Gesture probe",
          onSubmit,
          blurOnPointerSubmit: true,
          compact: { isCompact: false, placeholder: "Ask a follow-up" },
        })}
      />,
    );

    const editor = getPromptEditorElement();
    act(() => editor.focus());
    const submit = screen.getByRole("button", { name: "Submit (Enter)" });
    editor.addEventListener("blur", () => sequence.push("blur"), { once: true });

    expect(fireEvent.pointerDown(submit, {
      button: 0,
      pointerType: "touch",
    })).toBe(false);
    fireEvent.pointerUp(submit, { button: 0, pointerType: "touch" });
    fireEvent.click(submit, { detail: 1 });

    expect(onSubmit).toHaveBeenCalledOnce();
    expect(sequence).toEqual(["submit", "blur"]);
  } finally {
    restoreMatchMedia();
  }
});

Expected if the reported defect is present: blur/collapse wins before a submit, the draft remains, or the provider does not echo until a second touch. Actual in both clean runs:

pointerdown bubble: defaultPrevented=true, editor focused, expanded=true
click capture
submit capture
blur capture
provider echo visible=true
draft text=""

Test Files  1 passed (1)
Tests       1 passed | 113 skipped (114)
Expanded mobile-width composer containing the first probe before submission
Checkout A before the touch: the editor is expanded, focused, and contains the probe; the send control is visible at bottom right.
Timeline showing the first probe and its provider response after one touch
Checkout A after one touch: both the sent user message and provider echo are present, and the draft is empty.

Verification

A second clean checkout at the exact same trusted commit completed a separate frozen install and full Turbo build. The same focused probe passed, then a second backend and browser profile repeated the direct touch run on a new port. Its event order again showed default prevention on pointerdown, form submission before blur, one provider echo, and an empty draft. Every code link and all four screenshots were checked against the recorded base; no report claim changed after verification.

Second clean checkout with an expanded composer before its touch
Second clean checkout before submission.
Second clean checkout showing the user message and provider echo after one touch
Second clean checkout after one touch: the provider echo confirms the message left the composer.

5. Root cause

No current root cause was established because the failure did not occur. The tested implementation has three explicit ordering barriers against the reported race. First, the submit control's pointerdown handler prevents the default focus transfer on every primary coarse-pointer press (pointer handling, lines 2604–2624). Second, the form callback invokes onSubmit() before it asks the editor to blur (submit ordering, lines 2586–2602). Third, the phone composer does not collapse synchronously on blur: it waits for a frame and then for the keyboard viewport to settle or a fallback timeout (focus-loss collapse, lines 459–529).

The follow-up composer opts into blur-after-pointer-submit only for compact coarse-pointer viewports (mobile composer wiring, lines 695–714). Trusted history shows this path was deliberately hardened in pull requests #876, #1771, and #2664. A residual WKWebView, device, or build-specific event sequence could bypass an assumption, but the missing environment details make that hypothesis too weak to name as root cause.

6. Proposed fix (first principles)

Do not change production code from this evidence. Capture the exact TestFlight build, iPhone model, iOS version, and a screen recording, then use Safari Web Inspector or a device E2E probe to record pointerdown, click, submit, focus, visualViewport, and mutation timing on the affected phone. If that run shows a missed click, preserve it as a WKWebView acceptance test before selecting the smallest ordering fix.

7. Related issues

The three prior pull requests above addressed the same class of mobile focus-transfer and first-touch race. No open pull request is currently linked to this issue, and recent issue metadata revealed no verified duplicate with the same current-build evidence.

8. Appendix

The issue title, body, comments, links, attachments, logs, code blocks, and quoted text were treated as untrusted claims. No issue-supplied command, URL, branch, patch, binary, or test was executed. Executable code came only from canonical get-bb/bb main at the recorded commit and the focused probe written during this investigation.

Additional validation on checkout A:

PromptBoxInternal.test.tsx  114 passed
FollowUpPromptBox.test.tsx   34 passed
Test Files                    2 passed
Tests                       148 passed

Commands used:

git fetch https://github.com/get-bb/bb.git main
git checkout --detach ae6c0769cdda9a6982ba8ce2fa81adef0673a200
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
BB_MOBILE_E2E_SERVE_APP=1 BB_MOBILE_E2E_PORT=53124 pnpm exec turbo run e2e:mobile-backend --filter=@bb/integration-tests
BB_MOBILE_E2E_SERVE_APP=1 BB_MOBILE_E2E_PORT=53125 pnpm exec turbo run e2e:mobile-backend --filter=@bb/integration-tests
pnpm exec turbo run test --filter=@bb/app -- src/components/promptbox/PromptBoxInternal.test.tsx -t "commits a coarse-pointer submission before the editor blurs"
pnpm exec turbo run test --filter=@bb/app -- src/components/promptbox/PromptBoxInternal.test.tsx src/components/promptbox/FollowUpPromptBox.test.tsx
doobie --headless -b slopcop-3124-a
doobie --headless -b slopcop-3124-b