🩹 fix: Sync ControlCombobox popover width with trigger after layout changes#12887
Conversation
…hanges The popover width was measured once on mount via offsetWidth. When the agent builder side panel opens after a page reload with the sidebar collapsed, the trigger button is initially measured during the layout transition (~26px) and never re-measured, leaving the agent select dropdown rendered at the far left with no options fully visible. Use a ResizeObserver to keep buttonWidth in sync with the trigger's actual width whenever it resizes, then disconnect on unmount.
There was a problem hiding this comment.
Pull request overview
Fixes a ControlCombobox sizing bug in the client UI where the Agent Builder selector popover could render at an incorrect (stale) width after layout transitions, by keeping the popover width synchronized with the trigger’s rendered width.
Changes:
- Replace one-time
offsetWidthmeasurement with aResizeObserverthat updatesbuttonWidthas the trigger resizes. - Add Jest regression tests covering initial sizing, resize updates, and observer cleanup on unmount.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/client/src/components/ControlCombobox.tsx | Observes trigger width changes and updates popover inline width accordingly. |
| packages/client/src/components/ControlCombobox.spec.tsx | Adds regression/unit tests to validate popover width sync behavior and observer disconnection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@danny-avila ready for review! |
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…width branches Address review feedback: - Use button.offsetWidth as the ResizeObserver fallback instead of entry.contentRect.width to avoid a content-box vs border-box mismatch in pre-2022 browsers that ship ResizeObserver without borderBoxSize. - Add tests for the three previously-untested branches: isCollapsed=true (no observation of the trigger), ResizeObserver unavailable (sync-only measurement), and zero-width entries (state unchanged).
Add a test that drives the ResizeObserver callback with borderBoxSize absent and divergent contentRect.width vs offsetWidth (251 vs 275). The fix would silently revert to entry.contentRect.width without this test failing, so this pins the chosen fallback semantics.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…hanges (danny-avila#12887) * 🩹 fix: Sync ControlCombobox popover width with trigger after layout changes The popover width was measured once on mount via offsetWidth. When the agent builder side panel opens after a page reload with the sidebar collapsed, the trigger button is initially measured during the layout transition (~26px) and never re-measured, leaving the agent select dropdown rendered at the far left with no options fully visible. Use a ResizeObserver to keep buttonWidth in sync with the trigger's actual width whenever it resizes, then disconnect on unmount. * test: cover ControlCombobox isCollapsed, no-ResizeObserver, and zero-width branches Address review feedback: - Use button.offsetWidth as the ResizeObserver fallback instead of entry.contentRect.width to avoid a content-box vs border-box mismatch in pre-2022 browsers that ship ResizeObserver without borderBoxSize. - Add tests for the three previously-untested branches: isCollapsed=true (no observation of the trigger), ResizeObserver unavailable (sync-only measurement), and zero-width entries (state unchanged). * test: lock the button.offsetWidth fallback against revert Add a test that drives the ResizeObserver callback with borderBoxSize absent and divergent contentRect.width vs offsetWidth (251 vs 275). The fix would silently revert to entry.contentRect.width without this test failing, so this pins the chosen fallback semantics. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
…hanges (danny-avila#12887) * 🩹 fix: Sync ControlCombobox popover width with trigger after layout changes The popover width was measured once on mount via offsetWidth. When the agent builder side panel opens after a page reload with the sidebar collapsed, the trigger button is initially measured during the layout transition (~26px) and never re-measured, leaving the agent select dropdown rendered at the far left with no options fully visible. Use a ResizeObserver to keep buttonWidth in sync with the trigger's actual width whenever it resizes, then disconnect on unmount. * test: cover ControlCombobox isCollapsed, no-ResizeObserver, and zero-width branches Address review feedback: - Use button.offsetWidth as the ResizeObserver fallback instead of entry.contentRect.width to avoid a content-box vs border-box mismatch in pre-2022 browsers that ship ResizeObserver without borderBoxSize. - Add tests for the three previously-untested branches: isCollapsed=true (no observation of the trigger), ResizeObserver unavailable (sync-only measurement), and zero-width entries (state unchanged). * test: lock the button.offsetWidth fallback against revert Add a test that drives the ResizeObserver callback with borderBoxSize absent and divergent contentRect.width vs offsetWidth (251 vs 275). The fix would silently revert to entry.contentRect.width without this test failing, so this pins the chosen fallback semantics. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
Summary
Fixes a UI bug where the Agent Builder's agent selector dropdown renders in the wrong place (anchored to the very far left of the viewport, only ~26px wide, with no options fully visible) under the following scenario:
Root cause
packages/client/src/components/ControlCombobox.tsxmeasures the trigger'soffsetWidthonce on mount viauseEffect, and applies that as the inlinewidthof the AriakitSelectPopover. When the Agent Builder side panel mounts after a page reload with the sidebar collapsed, the trigger button is measured during the side panel's expansion transition (~26px) and is never re-measured. The popover is then rendered against that stale width, causing the misplacement.Fix
Use a
ResizeObserveron the trigger button to keepbuttonWidthin sync with the trigger's actual rendered width. The observer is disconnected on unmount, and the implementation gracefully no-ops in environments withoutResizeObserver.Change Type
Testing
Manual reproduction (before the fix)
Manual verification (after the fix)
Repeated the exact same steps:
document.querySelector('.animate-popover')thatstyle.width === '275px'andgetBoundingClientRect().left === trigger.left.Automated regression tests
Added
packages/client/src/components/ControlCombobox.spec.tsx:uses the button width measured on mount when layout is stable— sanity check that the existing on-mount measurement still works.updates the popover width when the trigger resizes after mount (regression: agent select dropdown rendering at narrow width)— directly reproduces the bug: simulates the trigger being measured at 26px on mount, asserts the popover starts at 26px, then fires aResizeObservercallback at 275px and asserts the popover updates to 275px.disconnects the ResizeObserver on unmount— ensures no observer leak.Run with:
All 3 tests pass.
Test Configuration:
docker compose up -d --buildhttp://localhost:3080Checklist