Skip to content

fix(app): keep the @ menu open while it searches - #59425

Merged
kitlangton merged 2 commits into
devfrom
at-popup-flicker-dev
Oct 26, 2026
Merged

kitlangton merged 2 commits into
devfrom
at-popup-flicker-dev

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

The @ menu in the composer no longer jumps or empties out on every keystroke.
Typing after @ shows files, agents and skills that match. While the next file search was in flight, the menu dropped its file rows, so it shrank to just the agents and skills, then grew back when results arrived. Agents, skills and the slash menu are not affected.

🔍 Origin: seen in real use, typing @re… in the desktop app. Same fix as #59424 on beta; dev has the identical code.

❌ Before

  1. Type @re. File results for re show.
  2. Type s. ❌ The file rows vanish at once, so the menu shrinks (or shows "No matching items").
  3. About 150 ms plus the server's reply later, files for res arrive and the menu grows back.

✅ After

  1. Type @re. File results for re show.
  2. Type s. The re results stay in place while res is searched.
  3. The res results replace them in one step. The highlighted row stays highlighted if it is still in the list.
- keystroke -> file rows = []        -> wait for search -> file rows = new results
+ keystroke -> keep last file rows   -> wait for search -> file rows = new results
+ menu closed, empty query, or a space -> file rows = []

📊 Proof

Case Before After
Next search still pending ❌ file rows gone, menu shrinks ✅ last rows stay, same height
Next search returns ✅ new rows ✅ new rows, highlight kept
Search returns nothing ✅ "No matching items" ✅ "No matching items"

Before, typing @research with a 250 ms search:

at-menu-before.mp4

After:

at-menu-after.mp4

The new component test fails on the current branch (old rows gone while the next search is pending) and passes with the change, 10 of 10 repeats.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none">
  <style>
    .caret { animation: blink 1s steps(1) infinite; }
    .row2 { animation: swap 2.4s ease-in-out infinite; }
    @keyframes blink { 50% { opacity: 0; } }
    @keyframes swap { 0%, 40% { opacity: 1; } 50% { opacity: .6; } 60%, 100% { opacity: 1; } }
    @media (prefers-reduced-motion: reduce) { .caret, .row2 { animation: none; } }
  </style>
  <rect x="20.5" y="4.5" width="199" height="34" rx="6" stroke="currentColor" stroke-opacity=".25"/>
  <rect x="25" y="9" width="190" height="11" rx="3" fill="currentColor" fill-opacity=".1"/>
  <rect x="30" y="13" width="70" height="3" rx="1.5" fill="currentColor" fill-opacity=".6"/>
  <g class="row2"><rect x="30" y="27" width="96" height="3" rx="1.5" fill="currentColor" fill-opacity=".55"/></g>
  <rect x="20.5" y="42.5" width="199" height="25" rx="6" stroke="currentColor" stroke-opacity=".35"/>
  <text x="30" y="59" font-family="ui-monospace, monospace" font-size="10" fill="currentColor" fill-opacity=".8">@res</text>
  <rect class="caret" x="55" y="50" width="1" height="11" fill="currentColor"/>
</svg>

👀 Read: interaction.ts, the file search. It no longer tags results with the query they came from, so the list can briefly show files for the previous query; that is the point. The rest is routine.

The clips use the composer story with a simulated 250 ms file search.

Checks run
  • bun typecheck in packages/app: no type errors
  • component tests were run on the beta PR (cherry-picked unchanged): the new test fails without the fix and passed 10 of 10 repeats with it

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works. With the PR's slow-file-search story applied to the base, the new component test fails: the previous file rows vanish while the next search is pending. With this change the rows stay until the new results replace them. The test passed 5 of 5 repeats, and the full composer.spec.ts component suite passes. tsc -b in packages/app is clean. Screenshots below show the same pending moment on the base and with the PR.

The public API is unchanged: this only touches the app's internal composer state and a story.

One small inline note: a failed search now leaves the previous query's results on screen.

Before

After

void Promise.resolve(input.searchContextFiles(query, controller.signal)).then(
(files) => !controller.signal.aborted && setFound({ query, files }),
(files) => !controller.signal.aborted && setFound("files", files),
() => undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that results are kept from one query to the next, a failed search leaves the previous query's file rows listed for the new query until another search succeeds. Before this change they disappeared. Clearing them on failure, for example () => !controller.signal.aborted && setFound("files", []), would stop out-of-date rows from staying around when the server errors.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit only adds a blank line in the story, so there was nothing new to check. My earlier note about rows from a failed search staying listed for the new query is still open. The public API is unchanged.

@kitlangton
kitlangton merged commit e1eefca into dev Oct 26, 2026
19 checks passed
@kitlangton
kitlangton deleted the at-popup-flicker-dev branch October 26, 2026 22:54
@amrkmn amrkmn mentioned this pull request Oct 27, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant