fix(web): close reasoning picker after selection - #4758
nithish6541 wants to merge 3 commits into
UI Consistency: 5 findings in TraitsPicker.tsx
Findings
apps/web/src/components/chat/TraitsPicker.tsx
- Line 330 / lines 381-387 —
cursor-pointerduplicatesMenuRadioItem's contract. The primitive already setscursor-pointerplusdata-disabled:cursor-not-allowedin its base class string (apps/web/src/components/ui/menu.tsx), so the new call-site class changes nothing and moves cursor ownership out of the primitive. - Line 522 —
cursor-pointeronMenuTriggeris a no-op. The trigger rendersComposerControl→Button, andbuttonVariantsalready includescursor-pointer; the class also splits cursor ownership between the trigger and the rendered control. - Lines 528-529 —
cursor-pointeradded to bothComposerControllayout branches. Same duplication: these strings should carry contextual layout/width only, with cursor owned byButton. - Line 559 — second close mechanism for the same interaction. The radio items already carry
closeOnClickat the merge base (upstream #5879), so the newonOptionSelected→setIsMenuOpen(false)path duplicates existing close behavior instead of relying on the primitive contract. - Line 559 — clobberable prop order.
onOptionSelectedis declared onTraitsMenuContentProps, whichTraitsPickeraccepts but does not destructure, so a consumer-suppliedonOptionSelectedlands in thepersistencerest object and, because{...persistence}is spread after this prop, silently overrides the menu-close handler. Fix by destructuringonOptionSelectedinTraitsPickeror spreadingpersistencebefore it.
Five inline comments were posted with the smallest expected fixes.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Verification performed
- Read the full head revision of
apps/web/src/components/chat/TraitsPicker.tsx(c4fdd43). - Confirmed primitive ownership of cursor state:
apps/web/src/components/ui/menu.tsx—MenuRadioItembase classes containcursor-pointeranddata-disabled:cursor-not-allowed.apps/web/src/components/ui/button.tsx—buttonVariantsbase containscursor-pointer;ComposerControl(apps/web/src/components/chat/ComposerControl.tsx) rendersButtonand adds no cursor override.
- Grepped
apps/web/src/**/*.cssforcursordeclarations to rule out a global rule that would suppress the primitive cursor inside menu popups; the only match is the unrelated.settings-sliderrule. - Checked history:
d4321005("fix(web): trait menu closes after you pick a level", #5879) already addedcloseOnClickto both radio groups, and it is present at the merge base, so the menu-close behavior exists without the new callback. - Checked the controlled-open state (
isMenuOpen) exists at the merge base (2773229), so this PR only adds the extra imperative close call. - Reviewed the only other consumer path (
apps/web/src/components/chat/composerProviderState.tsxrenderTraitsControl) — it does not currently passonOptionSelected, so finding 5 is a latent hazard introduced by the new prop rather than a live break.
No visual-regression risk was identified: all class additions are duplicates of existing primitive declarations, and disabled-state cursor behavior is preserved by the higher-specificity data-disabled: variant.
🔍 Agent activity — 2 tool calls
💭 I'll examine the file to see what changes were made. I need to check menu.tsx to see if MenuItem and MenuRadioItem already have cursor-pointer styling, and whether MenuTrigger uses a render prop that might cause className conflicts. I should also look at ComposerControl to understand how it's structured. I'm noticing that
MenuRadioItemalready hascursor-pointerbuilt into its base classes, so adding it again at the call sites would be redundant. The real issue is how tailwind-merge handles the interaction betweencursor-pointerand thedata-disabled:cursor-not-allowedvariant—since they're in different modifier groups, the call-site class might not properly override the disabled state behavior. Actually, looking at CSS specificity, the attribute selector indata-disabled:cursor-not-allowedhas higher specificity than the unprefixedcursor-pointer, so the disabled cursor would win anyway. But this redundancy is still worth flagging as a violation of the primitive's ownership of the cursor property—the call sites shouldn't be duplicating what the component already declares. Now I'm checking the Button primitive to see if it has similar issues. Button likely has cursor-pointer built in. I'm noticing theonOptionSelectedcallback combined withcloseOnClickon MenuRadioItem could trigger a double-close, though that's not harmful. The disabled case whereultrathinkInBodyTextis true prevents the close anyway since the item is disabled. I should check if TraitsMenuContent is used elsewhere and whether this menu-close behavior causes issues at those other call sites. I'm noticing a discrepancy between the line numbers from my view and what git grep is showing — the view had TraitsMenuContent at line 222, but grep reports it at 226 with TraitsMenuContentImplProps at 221. Both should be referencing the same commit (c4fdd43), so it's strange that the results don't match up. Let me check if the PR head has been updated since I last viewed it, or if git grep is pulling from a different revision than I expected. I'll fetch the file directly at c4fdd43 using git show to verify what's actually there. I see there's a recent commit on main that might already handle the trait menu closing behavior. Let me check the diff to understand what changes are actually in this branch versus what's already been merged from main — the merge base should clarify what's new here versus what's already implemented. Looking at the line numbers in the diff, it seems the default revision is pointing to main rather than the specific commit, but that's not the key issue. What matters is whether main already has the menu closing logic viacloseOnClick— and it does, from commit #5879. So this PR appears to be adding a second closing mechanism withonOptionSelectedand a controlledopenstate, which might be redundant. I need to check whether the controlled state itself is new in this PR or if it already existed at the merge base. The diff only showsonOptionSelected={() => setIsMenuOpen(false)}being added, suggestingisMenuOpenwas already there. Since main includescloseOnClick, the PR's imperative close through state is potentially redundant — two separate paths closing the menu. That could be a duplication concern, though it's mild since both mechanisms should work together. The more subtle issue is thatonOptionSelectedis now a new optional behavioral prop on TraitsMenuContent, which changes its contract. But here's the real problem:TraitsPickerusesTraitsMenuContentPropsfor its own props type, so consumers can passonOptionSelectedtoTraitsPickereven though it doesn't explicitly handle it. SinceTraitsPickerdoesn't destructureonOptionSelected, it ends up in thepersistencespread that gets passed toTraitsMenuContent— potentially overriding the internalsetIsMenuOpenclose handler depending on prop order. I should check if any consumers are actually passingonOptionSelectedtoTraitsPickerand whether thepersistenceobject contains unexpected keys. The fix would be to either reorder the spreads or explicitly destructureonOptionSelectedinTraitsPickerto prevent it from leaking through. Now looking at the cursor-pointer findings...MenuRadioItemalready appliescursor-pointerat the base level, so it's redundant at the call sites. ForMenuTrigger, Base UI's render mechanics merge the trigger's className with the rendered element's className by concatenating them, soComposerControlends up receiving both thecursor-pointerclass and whatevertriggerClassNameprops are passed through. But here's the thing:Button's base styles already includecursor-pointer, andComposerControldoesn't override it withcursor-defaultor anything else. So all three places addingcursor-pointerare just duplicating what the primitives already declare — this entire part of the PR is essentially a no-op. I've identified two issues to report: first, there's redundantcursor-pointerclasses scattered across MenuRadioItem and Button/ComposerControl/MenuTrigger that duplicate what the primitive already owns, creating maintenance drift. Second, there's a prop-order bug where the{...persistence}spread comes afteronOptionSelected, potentially clobbering the close handler sinceonOptionSelectedis part of TraitsMenuContentProps but isn't being destructured properly—I need to check composerProviderState to see if any consumers are actually passing this prop. Main already addressed the close behavior throughcloseOnClick, but this PR introduces a redundantonOptionSelectedcallback that duplicates that functionality by imperatively closing the menu from TraitsPicker — which means the menu closes even when it shouldn't in the consumer's own container. The redundancy isn't harmful in practice sincecloseOnClickwould close it anyway, but it creates an unnecessary prop-ordering hazard. I need to check whether the controlled open state for TraitsPicker was also part of this PR or if it existed at the merge base. The cursor-pointer additions are redundant since the base primitives already include them, and the close behavior duplicates what's already handled by closeOnClick. This PR essentially duplicates both the primitive's cursor styling and existing close functionality, which constitutes a concrete ownership violation—call sites shouldn't be re-applying cursor-pointer when the primitive already owns that styling. There's also a real prop-clobbering hazard: spreading persistence after onOptionSelected means the rest type includes onOptionSelected as an optional property, which would override the explicitly set close handler. No consumer currently passes it, so it's a latent bug rather than an active one, but the patch makes this pre-existing pattern relevant and reportable. On keyboard interaction, imperatively closing on selection means arrow+Enter also closes the menu just like closeOnClick does, which is fine. And with MenuTrigger's className merging through Base UI's render prop system, the cursor-pointer class gets concatenated with ComposerControl's className without issues, though the merge happens inside Trigger's useRender before Button receives the combined className. The cursor-pointer duplication across four call sites is actually a consistency violation — the primitive already owns that styling, so repeating it at each call site represents drift from the primitive contract. I'm going to report this as a concrete finding and fail the check. But I should verify whether the PR's intent was to fix a missing cursor. If Button and MenuRadioItem already have cursor-pointer built in, then adding it again would be redundant. Let me check if something's overriding the cursor elsewhere — maybe a global rule in the stylesheet or a Base UI popup applying cursor-default to menu items. I'm verifying the context at line 868. The cursor-pointer additions turned out to be unnecessary. Now I'm moving on to add inline comments to the TraitsPicker component file, targeting specific lines in the diff where I can post feedback on the right side. I'm deciding how to structure the comments — I could combine them or split them across multiple lines. The challenge is that inline suggestions need to stay within the diff boundaries, so I'm considering whether to use a unified diff snippet in the comment rather than trying to create a multi-line suggestion block that might reference non-diff context lines. I'll post a separate comment on line 528 with a suggestion to clean up the conditional styling, and then add another comment on line 522 addressing the MenuTrigger className issue. Then I need to look at line 559 where there's a potential redundancy with the onOptionSelected prop. On line 559, spreadingpersistenceafteronOptionSelectedcreates a prop-clobbering hazard—if a consumer passesonOptionSelectedin the persistence object, it silently overrides the close handler. Plus, sincecloseOnClickalready handles menu closure upstream, the extra callback is redundant; I should either destructureonOptionSelectedin TraitsPicker or move the spread before it. I'll combine both the redundancy and prop-order issue into one concise comment. I need to verify that Base UI's closeOnClick actually handles all the cases properly—the upstream commit claims it's fixed and it's on main, so the callback would be redundant. But I should check the prompt-injected select where handleSelectChange modifies the prompt state; that still uses closeOnClick, which is consistent. For comment 4, I'll frame it as the callback duplicating closeOnClick functionality, and if they want to keep it, the spread order becomes a potential issue—I'll ask for the simpler fix. Let me double-check the line numbers against the current file: 330 for the className inside MenuRadioItem, 385, 522 for MenuTrigger className, 528-529, and 559 for onOptionSelected all check out. Ready to post the review.
1. modify_pr — ## UI consistency review The pointer-cursor additions in this PR duplicate cursor state that the shared primitives already own, so they are no-ops at the call site: - Button's base buttonVariants already includes cursor-pointer (apps/web/src/components/ui/button.tsx), which ComposerControl renders through — so the cursor-pointer on MenuTrigger and on both ComposerControl class branches adds nothing. - MenuRadioItem's base classes already include cursor-pointer plus data-disabled:cursor-not-allowed (apps/web/src/components/ui/menu.tsx), so the per-item className="cursor-pointer" only re-states the primitive contract. Separately, the menu already closes on pick via closeOnClick on the radio items (added upstream in #5879, present at the merge base), so the new onOptionSelected close path is a second mechanism for the same behavior — and it is passed before {...persistence}, which makes it clobberable. Details inline.
💭 I need to revise that comment on lines 528-529 since the suggestion I left was sloppy. Let me pull up the review comments to find the ID so I can edit it. I need to fix a malformed suggestion in comment 3791096119 using a PATCH request.
2. modify_pr — PATCH repos/{owner}/{repo}/pulls/comments/3791096119
Agent Credits: 110 credits