You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
web: project selector caret flashes on hover — search input and menu fight over DOM focus #56868
Hovering rows in the new-session project selector makes the caret in the
"Search projects" input flash. The DOM focus is stolen and restored on every
hover, which also restarts the caret blink. Keyboard users lose typing focus
into the search box mid-hover.
Repro
Open the chat/new-session page in the web app
Open the project selector dropdown (bottom bar project button)
Sweep the mouse across the project rows
Observed: caret blinks off/on continuously while moving across rows. Expected: caret stays steady; hover only changes the row highlight.
Evidence (focus log, pre-fix)
A focusin listener on the page while sweeping three rows logs:
option:project::C%3A%2FOpenCode%2FHoverAlpha
DIV
option:project::C%3A%2FOpenCode%2FHoverBeta
DIV
option:project::C%3A%2FOpenCode%2FHoverGamma
Every hover steals focus from the search input (row focus + content focus)
and the selector's focusSearch() (setTimeout + rAF .focus()) puts it
back — a focus tug-of-war that flaps per hover and per pointer move.
Root cause
Two competing behaviors:
Kobalte Menu.Item.onPointerMove calls focusWithoutScrolling(row) on
every mouse move, and row pointerleave focuses the menu content
(_focusContent()).
packages/app/src/new-session/project/selector.tsx compensates in onMouseEnter with focusSearch() (rAF .focus() on the search input)
to keep search-as-you-type working.
The same compensation pattern exists in packages/app/src/providers/models/select-dialog.tsx (model selector).
Suggested fix
While an editable element inside the menu has focus, pointer hover should
never move DOM focus — visual highlight is already handled app-side
(active + classList). Applied at the three steal sites in @kobalte/core
(_focusContent, item onPointerMove, sub-trigger onPointerMove, the
latter keeping submenu hover-open). Note pointerleave is not cancelable,
so a defaultPrevented guard alone can't fix it.
Regression test: packages/app/e2e/regression/project-selector.spec.ts
(focus retention while hovering + highlight/aria-activedescendant/search
guards) — RED before the patch, green after; 47 related e2e tests pass.
Fix is prepared as a @kobalte/core patch (patches/@kobalte%2Fcore@0.13.13.patch)
following the repo's existing patched-dependencies convention.
Thanks for the detailed report. I reproduced this on the latest dev (501b15a) in the web app.
Steps: I opened a new-session draft with three projects (HoverAlpha, HoverBeta, HoverGamma), opened the project selector from the bottom bar and moved the mouse across the rows while logging focusin events.
Focus moves on every row I hover: the row, then Search projects, then the row again, then the menu content (prompt-project-menu). This matches the log in your report. The Playwright script below reproduces it; with the current code its focus assertion fails.
I only tested the project selector; I haven't checked the model selector you mentioned.
// Run from a dev checkout: copy to packages/app/e2e/regression/ and run// `cd packages/app && bunx playwright install chromium && bunx playwright test e2e/regression/project-selector-focus.spec.ts`import{expect,test}from"@playwright/test"import{openDraft}from"../utils/workspace"test.use({serviceWorkers: "block",viewport: {width: 1280,height: 900}})test("hovering project rows keeps DOM focus in the search input (#56868)",async({ page })=>{constdirs=["C:/OpenCode/HoverAlpha","C:/OpenCode/HoverBeta","C:/OpenCode/HoverGamma"]awaitopenDraft(page,{name: "HoverAlpha",seed: {projects: {local: dirs.map((worktree)=>({ worktree,expanded: true}))},},})awaitpage.locator('[data-component="new-session"] [data-action="prompt-project"]').first().click()constsearch=page.getByPlaceholder(/Searchprojects/i)awaitexpect(search).toBeFocused()awaitpage.evaluate(()=>{;(windowasany).__focusLog=[]document.addEventListener("focusin",(e)=>{constel=e.targetasHTMLElement;(windowasany).__focusLog.push(el.id||el.getAttribute("placeholder")||el.tagName)})})constrows=page.locator('[role="menuitemradio"]')constcount=awaitrows.count()console.log("rows:",count,awaitrows.allTextContents())for(leti=0;i<count;i++){constbox=(awaitrows.nth(i).boundingBox())!for(letx=20;x<120;x+=20)awaitpage.mouse.move(box.x+x,box.y+box.height/2)}constlog=awaitpage.evaluate(()=>(windowasany).__focusLog)console.log("FOCUS LOG:\n"+log.join("\n"))awaitpage.screenshot({path: "hover.png"})expect(log).toEqual([])})
Status update after the location redesign on dev: the dropdown from the original repro is gone (the project trigger now opens the location dialog), so those exact steps no longer reproduce. The same focus fight survived in the re-homed searchable worktree menu (session/composer/location-missing.tsx — its keydown listener that sends typing back to the search field is working around precisely this). PR #56873 has been merged with current dev and re-homed: the guard is unchanged, the Enter-forwarding moved to that menu, and the regression case now lives in session-location-sync.spec.ts. Keeping this issue for the PR to close.
Activity
opencode-agent commentedon Oct 18, 2026
Thanks for the detailed report. I reproduced this on the latest
dev(501b15a) in the web app.Steps: I opened a new-session draft with three projects (HoverAlpha, HoverBeta, HoverGamma), opened the project selector from the bottom bar and moved the mouse across the rows while logging
focusinevents.Focus moves on every row I hover: the row, then Search projects, then the row again, then the menu content (
prompt-project-menu). This matches the log in your report. The Playwright script below reproduces it; with the current code its focus assertion fails.I only tested the project selector; I haven't checked the model selector you mentioned.
Reproduction script (project-selector-focus.spec.ts)
Run it:
bun project-selector-focus.spec.tstsehla42 commentedon Oct 27, 2026
Status update after the location redesign on
dev: the dropdown from the original repro is gone (the project trigger now opens the location dialog), so those exact steps no longer reproduce. The same focus fight survived in the re-homed searchable worktree menu (session/composer/location-missing.tsx— its keydown listener that sends typing back to the search field is working around precisely this). PR #56873 has been merged with currentdevand re-homed: the guard is unchanged, the Enter-forwarding moved to that menu, and the regression case now lives insession-location-sync.spec.ts. Keeping this issue for the PR to close.