-
Notifications
You must be signed in to change notification settings - Fork 0
feat: migrate UI to Base UI, add MCP integration, fix multi-chat reliability, curate model catalog #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add @base-ui/react dependency - Create asChild → render prop adapter utility with tests - Add research on Radix-to-Base-UI CSS variable mapping - Add Base UI migration plan - Add create-plan skill template Co-authored-by: Cursor <cursoragent@cursor.com>
Replace @radix-ui/react-* with @base-ui/react/* across 22 component files. Updates include Radix Slot/asChild to Base UI render prop via adaptAsChild adapter, Content→Popup, Overlay→Backdrop renames, data-[state=open/closed]→data-[open/closed] attribute migration, Positioner wrappers for tooltip/popover/hover-card/dropdown-menu, and --radix-* → --transform-origin CSS variable updates. Backward- compatible prop aliases preserved where needed. Co-authored-by: Cursor <cursoragent@cursor.com>
…tives Migrate accordion, context-menu, menubar, navigation-menu, radio-group, scroll-area, select, slider, and toggle-group to @base-ui/react. Updates include Radix data-[state=open/closed] → Base UI data-[open/closed], Positioner/Popup patterns, and renamed sub-components (e.g. SubTrigger → SubmenuTrigger, ItemIndicator → CheckboxItemIndicator). Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace @radix-ui/react-collapsible with @base-ui/react/collapsible - Replace Radix Slot with Base UI useRender + adaptSlotAsChild in form and sidebar - Update data attributes from data-[state=open] to data-[open] pattern - Update CSS variable from --radix-collapsible-content-height to --collapsible-panel-height Co-authored-by: Cursor <cursoragent@cursor.com>
…gration config Remove 26 @radix-ui/* packages from dependencies, switch Shadcn style to "base-vega" for future Base UI component generation, update CSS variable naming (--radix-popper-anchor-width → --anchor-width), and document the Base UI migration in components/CLAUDE.md. Co-authored-by: Cursor <cursoragent@cursor.com>
Update all documentation references from Radix to Base UI and fix collapsible data attributes (data-[state=open] → data-[open]) in chain-of-thought, steps, and tool components. Mark migration plan as completed. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Remove deprecated prop aliases (delayDuration, skipDelayDuration, position) from tooltip, select, and related components. Update all call sites to use native Base UI props directly. Simplify scroll viewport ref in chat preview. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5 issues found across 55 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="components/ui/breadcrumb.tsx">
<violation number="1" location="components/ui/breadcrumb.tsx:45">
P2: `useRender` is a hook, so this module must be a Client Component. Without a `"use client"` directive, Next.js treats this file as a Server Component and will error when `useRender` is called. Add `"use client"` at the top of the file (or refactor to avoid hooks).</violation>
</file>
<file name="components/ui/select.tsx">
<violation number="1" location="components/ui/select.tsx:75">
P2: `alignItemWithTrigger` passed by callers is ignored and forwarded to `SelectPrimitive.Popup` because the local constant is hard-coded from `position` and the prop is never destructured. This prevents callers from overriding alignment and may pass an unsupported prop to Popup. Destructure `alignItemWithTrigger` from the props and default it to the Radix-compat behavior before passing the remaining props to Popup.</violation>
</file>
<file name="components/ui/form.tsx">
<violation number="1" location="components/ui/form.tsx:109">
P2: FormControl’s props type allows any ReactNode, but the implementation requires exactly one ReactElement via React.Children.only. This mismatch can cause runtime exceptions when a caller passes null, text, or multiple children. Tighten the prop type (or add a runtime guard) so invalid children are caught at compile time.</violation>
</file>
<file name="components/ui/context-menu.tsx">
<violation number="1" location="components/ui/context-menu.tsx:102">
P2: Wrap the submenu Positioner/Popup in `ContextMenuPrimitive.Portal` as per Base UI’s nested menu pattern; otherwise submenu content can be clipped or layered incorrectly because it isn’t portaled.</violation>
</file>
<file name="components/ui/popover.tsx">
<violation number="1" location="components/ui/popover.tsx:67">
P2: PopoverAnchor is now just a plain div, so it no longer wires into the popover context. Any usage expecting anchor positioning will break silently. Consider removing PopoverAnchor entirely or implementing it using a Base UI-supported anchor mechanism so it still affects positioning.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Greptile OverviewGreptile SummaryThis PR completes a large Radix → Base UI migration by replacing Radix primitives with Base UI primitives across the The new Merge blockers found are concentrated in wrapper/call-site integration: the header Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant App as app/components (call sites)
participant Wrappers as components/ui/* wrappers
participant Adapter as lib/as-child-adapter
participant BaseUI as @base-ui/react primitives
App->>Wrappers: Render <DropdownMenu>/<Trigger>/<Content>
Wrappers->>Adapter: adaptAsChild(asChild, children)
Adapter-->>Wrappers: { render?, children }
Wrappers->>BaseUI: <Trigger render={render}>{children}</Trigger>
Wrappers->>BaseUI: <Positioner><Popup ... /></Positioner>
Note over Adapter,Wrappers: If asChild=true + non-element children,
Note over Adapter,Wrappers: Children.only(...) throws (runtime crash).
Note over App,Wrappers: If Trigger used without children,
Note over App,Wrappers: trigger renders empty (header user-menu bug).
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 files reviewed, 3 comments
| function DropdownMenuSubContent({ | ||
| className, | ||
| side = "right", | ||
| sideOffset = 0, | ||
| align = "start", | ||
| alignOffset = -3, | ||
| ...props | ||
| }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { | ||
| }: React.ComponentProps<typeof DropdownMenuContent>) { | ||
| return ( | ||
| <DropdownMenuPrimitive.SubContent | ||
| <DropdownMenuContent | ||
| data-slot="dropdown-menu-sub-content" | ||
| side={side} | ||
| sideOffset={sideOffset} | ||
| align={align} | ||
| alignOffset={alignOffset} | ||
| className={cn( | ||
| "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", | ||
| "min-w-[8rem] origin-(--transform-origin)", | ||
| className | ||
| )} | ||
| {...props} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Submenu gets wrong slot
DropdownMenuSubContent passes data-slot="dropdown-menu-sub-content" into DropdownMenuContent, but DropdownMenuContent hardcodes data-slot="dropdown-menu-content" on the actual MenuPrimitive.Popup. As a result, submenu popups will be styled like the main menu content (and any data-slot-scoped CSS for submenus won't match).
To keep slot-based styling/test selectors correct, ensure the popup rendered for the submenu carries the dropdown-menu-sub-content slot (not the parent wrapper component).
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/ui/dropdown-menu.tsx
Line: 257:276
Comment:
**Submenu gets wrong slot**
`DropdownMenuSubContent` passes `data-slot="dropdown-menu-sub-content"` into `DropdownMenuContent`, but `DropdownMenuContent` hardcodes `data-slot="dropdown-menu-content"` on the actual `MenuPrimitive.Popup`. As a result, submenu popups will be styled like the main menu content (and any `data-slot`-scoped CSS for submenus won't match).
To keep slot-based styling/test selectors correct, ensure the popup rendered for the submenu carries the `dropdown-menu-sub-content` slot (not the parent wrapper component).
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
This results in an empty/unclickable menu trigger in the header. Wrap the Prompt To Fix With AIThis is a comment left during a code review.
Path: app/components/layout/user-menu.tsx
Line: 151:159
Comment:
**Dropdown trigger missing children**
`DropdownMenuTrigger` in `components/ui/dropdown-menu.tsx` destructures `children` and passes it through to `adaptAsChild`. In the header variant here, `<DropdownMenuTrigger>` is used without any children, so `adaptAsChild(false, undefined)` returns `{ children: undefined }` and the trigger renders no content.
This results in an empty/unclickable menu trigger in the header. Wrap the `Avatar` in the trigger (or use `asChild` and pass the `Avatar` as the child) so the trigger has content.
How can I resolve this? If you propose a fix, please make it concise. |
- Replace vaul (Radix-based) with vaul-base (Base UI port) for drawer - Import vaul-base/style.css in globals.css - Simplify adaptAsChild/adaptSlotAsChild: remove Children.only, graceful fallback for non-element children instead of throwing - Add asChild support to DrawerTrigger and DrawerClose via adapter - Wrap ContextMenu sub-content in Portal for proper layering - Map HoverCard openDelay to Base UI's delay prop - Add missing data-[closed]:animate-out to menubar content - Remove unused PopoverAnchor export - Remove stale data-[open] classes from dialog/sheet close buttons - Tighten FormControl children type to ReactElement - Add "use client" directive to breadcrumb - Document third-party deps (cmdk, vaul-base) in components/CLAUDE.md Co-authored-by: Cursor <cursoragent@cursor.com>
HoverCard: bridge Radix-style openDelay/closeDelay from Root to Base UI's Trigger via context, restoring delay behavior for source.tsx consumer. Menubar: add missing data-[closed]:animate-out on MenubarContent so exit animations play, and wrap MenubarSubContent in Portal for proper z-index. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="app/globals.css">
<violation number="1" location="app/globals.css:3">
P2: Avoid importing CSS via a relative path into node_modules; it’s brittle and can break module resolution in different build setups. Use the package import instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
vaul-base's package.json exports field doesn't include ./style.css, causing Turbopack's enhanced-resolve to block the import and crash the dev server. Copied the CSS locally as a workaround. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
A large branch covering four main workstreams: migrating the component library from Radix to Base UI, adding Model Context Protocol (MCP) tool support, fixing multi-chat message reliability, and curating the model catalog.
1. Base UI Migration (Radix → Base UI)
Replaced all 21
@radix-ui/react-*dependencies with a single@base-ui/reactpackage. 35 UI components incomponents/ui/were migrated:Content→Popup,Overlay→Backdrop,Viewport→List,ScrollUpButton→ScrollUpArrow,Label→GroupLabel(select)position="popper"with explicit<Positioner>wrapper components usingside/align/sideOffsetpropsdata-[state=open]→data-[open],data-[state=closed]→data-[closed]throughout CSS classes--radix-*-transform-origin→--transform-origin,--radix-select-content-available-height→--available-height, etc.lib/as-child-adapter.tsto bridge Radix'sasChildboolean prop to Base UI'srenderprop pattern (with full test suite)2. MCP (Model Context Protocol) Integration
Full end-to-end MCP support, feature-flagged behind
ENABLE_MCP=true:Database layer (Convex):
mcpServers,mcpToolApprovals,mcpToolCallLogmcpServers.ts— CRUD with SSRF validation, server limit enforcement, encrypted auth storagemcpToolApprovals.ts— Per-tool granular approval/toggle with bulk syncmcpToolCallLog.ts— Audit trail with truncated previews and paginated queriesmessages.ts— Server-side idempotency guard (deduplicates bymessageGroupId + role + model)API routes (3 new):
app/api/mcp-servers/route.ts— Server CRUDapp/api/mcp-servers/status/route.ts— Health checkapp/api/mcp-servers/test/route.ts— Connection testCore library (
lib/mcp/):load-tools.ts— Orchestrates connecting to user's enabled MCP servers, loading tools with namespacing, and returning aToolSetforstreamText()url-validation.ts— SSRF defense: blocks private IPv4/IPv6 ranges, localhost, CGNAT, link-local, IPv4-mapped IPv6,.localhostnamescircuit-breaker.ts— Resilience pattern for MCP connections (failure threshold → open → half-open → recovery)truncation.ts— Output sanitization for tool resultsChat route integration (
app/api/chat/route.ts):ENABLE_MCP=truestreamText()with configurablemaxStepsmcp_tool_load(per-request) andmcp_tool_call(per-invocation)after()cleanup ensures MCP client connections are always closedSettings UI (3 new components):
mcp-server-form.tsx— Add/edit form with transport (SSE/HTTP) and auth type (none/bearer/header) optionsmcp-servers.tsx— Server list with enable/disable, test connection, deletemcp-tool-approvals.tsx— Granular per-tool approval togglesTests (4 new test files, 1442 lines):
circuit-breaker.test.ts,load-tools.test.ts,truncation.test.ts,url-validation.test.ts3. Multi-Chat Reliability Fixes
Fixed duplicate message groups and lost history in multi-model chat:
SubmissionRecord): Lifecycle-managed tracking of multi-model submissions — entries are only cleaned up when ALL models complete streaming, preventing premature bridge removal that caused duplicate groupsmessageGroupIdRefwith per-modelMap<string, string>, fixing stale closure bugs when submissions overlapmessageGroupId(UUID) instead of text content, with text-content fallback for backward compatibilityconvex/messages.tsaddmutation checks for existing message with samemessageGroupId + role + modelbefore insertinguseChathook messages are cleared aftercacheAndAddMessage, preventing stale live messages from producing duplicate groups4. Model Catalog Curation
Added: GPT-5.1 Instant, GPT-5.1 Thinking, Gemini 3 Pro (renamed from preview), Codestral, o4-mini
Removed: Claude 3 Haiku, Claude Opus 4, Gemini 1.5 Flash/Pro/8B, Gemini 2.0 Flash/Flash-Lite, GPT-4o Mini, GPT-4.1 Mini/Nano, GPT-4.5 Preview, GPT-5, GPT-5 Mini, o1, o3-mini, all Llama/Groq models (
llama.tsdeleted)Updated: OpenRouter Gemini model IDs to GA (removed
-previewsuffixes), removedpreviewtags from stable models5. UI/UX Improvements
sidebar-list.tsx, improved collapsible chevron rotationapp-info-content.tsx+app-info-trigger.tsx), updated settings iconlib/file-handling.ts6. Tooling & Infrastructure
^4.0.18) withvitest.config.tsandbun run testscriptbun run dev:cleanscript (clears.nextcache before dev)@ai-sdk/mcpto exact1.0.18(was^1.0.18)116 files changed · ~10,600 additions · ~1,560 deletions · 25 commits
Test plan
asChildpass-through (e.g., tooltip wrapping icon buttons), keyboard navigation, focus managementlocalhost,10.x.x.x,192.168.x.x,::1, andfe80::URLsbun run test— all tests pass (as-child-adapter, circuit-breaker, load-tools, truncation, url-validation)bun run lintandbun run typecheckpass cleanly