Replies: 2 comments
Still present in
|
Follow-up on
|
| press target | WebKit, no preventDefault |
WebKit, preventDefault on buttons |
|---|---|---|
a row <button> |
pointerdown → mousedown → focusout(relatedTarget=null), focus lands on body |
no blur at all; the focused row keeps focus |
provider heading / card padding / list scrollbar (not a <button>) |
focusout(relatedTarget=null) → body |
focusout(relatedTarget=null) → body (unchanged) |
So the mitigation does keep row clicks alive in WebKit, but a press that lands inside the card on something that is not a <button> still reports a destination-less blur, and onBlur still unmounts the card before the release. That case reproduces in Chromium too, and it is what the browser scenario now pins:
DSH_SNAPSHOT=replay npx vitest run --config vitest.web.config.ts apps/web/tests/declared-reasoning.e2e.ts
# Chromium + WebKit, 8/8 with the change; rebuilding the client bundles without it fails on
# WebKit at the press-on-the-provider-heading step: [role="menu"] count 0 right after mouse.down()
On the alternative above (if (!(event.relatedTarget instanceof Node)) return, i.e. no destination-less blur may adjudicate dismissal): it removes the failure class, at the cost of dismissal for every destination-less focus loss rather than the ones a press causes. A window/app switch and a scripted blur() also report relatedTarget === null, and the card closes on them today — the intent of the existing guard is that only a real departure closes it, and what it cannot tell is whether a press is in flight.
What we ship instead — let the press be the evidence, then let the blur judge:
// Recorded on `pointerdown` (capture, document) for as long as the press is held;
// `pointerup`, `pointercancel`, and the window losing focus all end it.
if (event.relatedTarget === null && pressInsideRef.current) {
// The press never left the card; the engine merely named no destination.
const lost = event.target
if (lost instanceof HTMLButtonElement) lost.focus() // keep the keyboard inside the card
return
}
close()Handing focus back matters for the second half of the report: without it the card survives the press but the keyboard sits on body, so the ↑/↓/Tab fallback is dead until the trigger is clicked again. The containment test both listeners need (trigger subtree ∪ portaled card) is extracted as insideCard and shared with the existing outside-mousedown listener, so "outside the card" means one thing in the file.
Where the change is
- branch:
satellite13/deepseek-harness→fix/webkit-model-select-click - PR from the fork: fix(web): keep the model picker open for presses inside its card satellite13/deepseek-harness#1 — upstream refuses
CreatePullRequestfor this account and issues are disabled here, so the branch currently lives in a fork; happy to move it wherever you want it delivered packages/client/ui-model-selection/tests/model-select.client.spec.tsx: two cases — an in-card press whose blur names no destination keeps the card open, restores the keyboard to the row, and the click after the release still submits; the same blur closes the card when no press is held or the press started outside itapps/web/tests/declared-reasoning.e2e.ts: the press-on-the-heading step above, inside the scenario that already runs Chromium and WebKit; committed ARIA goldens unchangedpnpm run typecheck, repository lint on the changed files, andpnpm run test:docs(20 gates) pass
Deliberately narrower than both proposals: a press that starts outside the card, and a blur with no press in flight, keep dismissing the card.
Uh oh!
There was an error while loading. Please reload this page.
Environment
Steps
Expected
The effort switches (and is persisted as the session/default selection).
Actual
Nothing happens: value stays the same, no toast, no console error, no network request.
The host side confirms the call never arrives — the session log has zero
model/selectionevents and the model-selection projection stays
{lastUsed: low, pending: null}.Keyboard (ArrowDown/Up + Enter) works, and
/modelworks.Evidence
Capture-phase listeners on the page while clicking one row:
Root cause
packages/client/ui-model-selection/src/client/ModelSelect.tsxThe menu is portaled into
document.body, and the seat root carries a blur guard:Safari never moves focus to a clicked
<button>, so pressing a row emitsfocusoutwithrelatedTarget === null. The guard fails,close()runs duringmousedown, React unmountsthe menu before
mouseup/click, and the row'sonClicknever fires. Chrome focuses theclicked button, so
relatedTargetis the row and the guard passes.The keyboard path is unaffected because it activates rows synthetically:
focused.click()— it never goes through mousedown/blur.Suggested fix
Suppress the mousedown focus shift for buttons inside the menu (verified locally on the
installed 0.1.7-alpha.1 client bundle).
preventDefault()is scoped to button targets soscrollbar drags inside a long model list still work, and the keyboard path is untouched:
Alternatives: make the
onBlurguard toleraterelatedTarget === nulland re-check focusafter a task, or activate rows from
onPointerUpinstead ofonClick.Impact
Any Safari user of the web GUI cannot change the model or the reasoning effort with the mouse.
All reactions