Skip to content

Unify radix components#2829

Merged
dimaMachina merged 9 commits intomainfrom
unify-radix-components
Mar 25, 2026
Merged

Unify radix components#2829
dimaMachina merged 9 commits intomainfrom
unify-radix-components

Conversation

@dimaMachina
Copy link
Copy Markdown
Collaborator

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: 76f4798

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@inkeep/agents-manage-ui Patch
@inkeep/agents-api Patch
@inkeep/agents-cli Patch
@inkeep/agents-core Patch
@inkeep/agents-email Patch
@inkeep/agents-mcp Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 25, 2026 8:20am
agents-docs Ready Ready Preview, Comment Mar 25, 2026 8:20am
agents-manage-ui Ready Ready Preview, Comment Mar 25, 2026 8:20am

Request Review

@vercel vercel Bot temporarily deployed to Preview – agents-docs March 25, 2026 07:01 Inactive
@dimaMachina
Copy link
Copy Markdown
Collaborator Author

@claude --review and add a changeset

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Mar 25, 2026

TL;DR — Replaces 16 individual @radix-ui/react-* package imports with the unified radix-ui meta-package across the entire agents-manage-ui component library. This follows the official Radix UI migration path (via the shadcn migrate codemod) to consolidate dependencies, simplify version management, and reduce package.json noise.

Key changes

  • Consolidated Radix dependency — Removed 16 separate @radix-ui/react-* packages from package.json and added a single radix-ui package
  • Migrated namespace imports in UI primitives — Replaced import * as XPrimitive from '@radix-ui/react-x' with import { X as XPrimitive } from 'radix-ui' across 21 UI component files
  • Migrated Slot imports — Changed import { Slot } from '@radix-ui/react-slot' to import { Slot as SlotPrimitive } from 'radix-ui' and updated all asChild usages to SlotPrimitive.Slot
  • Updated consumer components — Migrated 3 non-UI components that directly imported Radix primitives to use the new unified package

Summary | 28 files | 6 commits | base: mainunify-radix-components


Consolidated Radix dependency

Before: 16 individual @radix-ui/react-* dependencies listed in package.json (alert-dialog, avatar, checkbox, collapsible, dialog, dropdown-menu, label, popover, radio-group, scroll-area, select, separator, slot, switch, tabs, tooltip)
After: A single "radix-ui": "^1.4.3" dependency replaces all of them

The radix-ui meta-package re-exports every Radix primitive from a single entry point, eliminating version drift between individual packages and reducing lockfile churn. The 16 removed entries are replaced by one addition at the bottom of the dependencies block.

agents-manage-ui/package.json


Migrated namespace imports in UI primitives

Before: import * as DialogPrimitive from '@radix-ui/react-dialog'
After: import { Dialog as DialogPrimitive } from 'radix-ui'

Every UI wrapper component that previously used a wildcard namespace import from an individual Radix package now uses a named import aliased to the same *Primitive identifier. This means all downstream component code (referencing DialogPrimitive.Root, DialogPrimitive.Content, etc.) continues to work unchanged. The pattern applies uniformly to 17 primitives: AlertDialog, Avatar, Checkbox, Collapsible, Dialog, DropdownMenu, Label, Popover, RadioGroup, ScrollArea, Select, Separator, Sheet (backed by Dialog), Switch, Tabs, and Tooltip.

alert-dialog.tsxdialog.tsxselect.tsxtooltip.tsxdropdown-menu.tsxcheckbox.tsxpopover.tsxradio-group.tsxscroll-area.tsxseparator.tsxsheet.tsxswitch.tsxtabs.tsxlabel.tsxavatar.tsxcollapsible.tsx


Migrated Slot imports

Before: import { Slot } from '@radix-ui/react-slot' with usage as const Comp = asChild ? Slot : 'button'
After: import { Slot as SlotPrimitive } from 'radix-ui' with usage as const Comp = asChild ? SlotPrimitive.Slot : 'button'

The Slot primitive used for the asChild pattern in button, badge, button-group, sidebar, and form now comes from the unified package. Since the unified radix-ui export nests Slot under the Slot namespace (unlike the old flat export), every usage site was updated to SlotPrimitive.Slot. The form component also switches LabelPrimitive to a type-only import from the same package.

button.tsxbadge.tsxbutton-group.tsxsidebar.tsxform.tsx


Updated consumer components

Before: Feature components imported Radix primitives directly, e.g. import { TabsContent } from '@radix-ui/react-tabs', import { Dialog } from '@radix-ui/react-dialog', import type { CheckedState } from '@radix-ui/react-checkbox'
After: All three switch to import { X as XPrimitive } from 'radix-ui' and access sub-components via the namespace (e.g. RadixTabs.TabsContent)

Three non-UI components that bypassed the local UI wrappers to import Radix primitives directly were also migrated. chat-ui-code.tsx switches TabsContent to RadixTabs.TabsContent, delete-project-confirmation.tsx replaces its direct Dialog import with the local Dialog re-export from @/components/ui/dialog, and skill-selector.tsx changes the CheckedState type import to RadixCheckbox.CheckedState.

chat-ui-code.tsxdelete-project-confirmation.tsxskill-selector.tsxcolor-picker.tsxinfo-card.tsx

Pullfrog  | View workflow run | Triggered by Pullfrogpullfrog.com𝕏

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Mar 25, 2026

TL;DR — Replaces 16 individual @radix-ui/react-* package imports with the unified radix-ui meta-package across the entire agents-manage-ui component library. This follows the official Radix UI migration path (via the shadcn migrate codemod) to consolidate dependencies, simplify version management, and reduce package.json noise.

Key changes

  • Consolidated Radix dependency — Removed 16 separate @radix-ui/react-* packages from package.json and added a single radix-ui package
  • Migrated namespace imports in UI primitives — Replaced import * as XPrimitive from '@radix-ui/react-x' with import { X as XPrimitive } from 'radix-ui' across 21 UI component files
  • Migrated Slot imports — Changed import { Slot } from '@radix-ui/react-slot' to import { Slot as SlotPrimitive } from 'radix-ui' and updated all asChild usages to SlotPrimitive.Slot
  • Updated consumer components — Migrated 5 non-UI components that directly imported Radix primitives to use the new unified package
  • Polished naming consistency — Normalized import aliases to use the *Primitive convention throughout (e.g. RadixTabsTabsPrimitive, RadixCheckboxCheckboxPrimitive) and removed redundant sub-component prefixes (e.g. PopoverPrimitive.PopoverTriggerPopoverPrimitive.Trigger)
  • Added changeset — Patch changeset for @inkeep/agents-manage-ui documenting the migration

Summary | 29 files | 8 commits | base: mainunify-radix-components


Consolidated Radix dependency

Before: 16 individual @radix-ui/react-* dependencies listed in package.json (alert-dialog, avatar, checkbox, collapsible, dialog, dropdown-menu, label, popover, radio-group, scroll-area, select, separator, slot, switch, tabs, tooltip)
After: A single "radix-ui": "^1.4.3" dependency replaces all of them

The radix-ui meta-package re-exports every Radix primitive from a single entry point, eliminating version drift between individual packages and reducing lockfile churn. The 16 removed entries are replaced by one addition at the bottom of the dependencies block.

agents-manage-ui/package.json


Migrated namespace imports in UI primitives

Before: import * as DialogPrimitive from '@radix-ui/react-dialog'
After: import { Dialog as DialogPrimitive } from 'radix-ui'

Every UI wrapper component that previously used a wildcard namespace import from an individual Radix package now uses a named import aliased to the same *Primitive identifier. This means all downstream component code (referencing DialogPrimitive.Root, DialogPrimitive.Content, etc.) continues to work unchanged. The pattern applies uniformly to 17 primitives: AlertDialog, Avatar, Checkbox, Collapsible, Dialog, DropdownMenu, Label, Popover, RadioGroup, ScrollArea, Select, Separator, Sheet (backed by Dialog), Switch, Tabs, and Tooltip.

alert-dialog.tsx · dialog.tsx · select.tsx · tooltip.tsx


Migrated Slot imports

Before: import { Slot } from '@radix-ui/react-slot' with usage as const Comp = asChild ? Slot : 'button'
After: import { Slot as SlotPrimitive } from 'radix-ui' with usage as const Comp = asChild ? SlotPrimitive.Slot : 'button'

The Slot primitive used for the asChild pattern in button, badge, button-group, sidebar, and form now comes from the unified package. Since the unified radix-ui export nests Slot under the Slot namespace (unlike the old flat export), every usage site was updated to SlotPrimitive.Slot. The form component also switches LabelPrimitive to a type-only import from the same package.

button.tsx · badge.tsx · button-group.tsx · sidebar.tsx · form.tsx


Updated consumer components

Before: Feature components imported Radix primitives directly, e.g. import { TabsContent } from '@radix-ui/react-tabs', import { Dialog } from '@radix-ui/react-dialog', import { Popover } from '@radix-ui/react-popover'
After: All switch to import { X as XPrimitive } from 'radix-ui' and access sub-components via the namespace (e.g. TabsPrimitive.TabsContent)

Five non-UI components that bypassed the local UI wrappers to import Radix primitives directly were also migrated: chat-ui-code.tsx switches to TabsPrimitive.TabsContent, delete-project-confirmation.tsx replaces its direct Dialog import with the local re-export from @/components/ui/dialog, skill-selector.tsx changes the CheckedState type import to CheckboxPrimitive.CheckedState, color-picker.tsx uses PopoverPrimitive.Root/.Trigger/.Content, and info-card.tsx uses CollapsiblePrimitive.Root/.CollapsibleTrigger/.CollapsibleContent.

chat-ui-code.tsx · delete-project-confirmation.tsx · skill-selector.tsx · color-picker.tsx · info-card.tsx

Pullfrog  | View workflow run | Triggered by Pullfrogpullfrog.com𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Clean migration from individual @radix-ui/react-* packages to the unified radix-ui package, aligned with the shadcn/ui February 2026 changelog. All 16 individual Radix packages removed, single radix-ui dependency added, no remaining @radix-ui imports in source.

One nit on dependency ordering in package.json.

Pullfrog  | View workflow runpullfrog.com𝕏

"zod": "^4.3.6",
"zustand": "^5.0.7"
"zustand": "^5.0.7",
"radix-ui": "^1.4.3"
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.

Nit: radix-ui is placed after zustand, breaking the alphabetical ordering of dependencies. It should sit between prettier and react (around line 92).

This will likely be flagged by format/lint checks as well.

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(4) Total Issues | Risk: Low

🟡 Minor (4) 🟡

Inline Comments:

  • 🟡 Minor: color-picker.tsx:30 Incorrect sub-component access pattern (PopoverPrimitive.PopoverTriggerPopoverPrimitive.Trigger)
  • 🟡 Minor: color-picker.tsx:38 Incorrect closing tag
  • 🟡 Minor: color-picker.tsx:41 Incorrect sub-component access pattern (PopoverPrimitive.PopoverContentPopoverPrimitive.Content)
  • 🟡 Minor: color-picker.tsx:43 Incorrect closing tag

💭 Consider (3) 💭

Inline Comments:

  • 💭 Consider: chat-ui-code.tsx:2 Inconsistent import naming (RadixTabs vs *Primitive convention)
  • 💭 Consider: chat-ui-code.tsx:91-96 Direct radix-ui usage bypasses local styled wrapper
  • 💭 Consider: skill-selector.tsx:4 Inconsistent type import naming (RadixCheckbox vs *Primitive convention)

🧹 While You're Here (1) 🧹

🧹 1) package.json:109 Alphabetical ordering

Issue: The radix-ui dependency was added at the end of the dependencies list after zustand, breaking alphabetical ordering.

Why: The rest of dependencies are alphabetically sorted. Maintaining this order helps with readability and reduces merge conflicts.

Fix: Move "radix-ui": "^1.4.3" to its correct position between pino-pretty and react.


💡 APPROVE WITH SUGGESTIONS

Summary: This is a clean migration from individual @radix-ui/react-* packages to the unified radix-ui package following shadcn's guidelines. The core migration is correct. The minor issues are around sub-component naming in color-picker.tsx (using PopoverPrimitive.PopoverTrigger instead of PopoverPrimitive.Trigger) and some naming convention inconsistencies. None are blocking.

Discarded (3)
Location Issue Reason Discarded
info-card.tsx:40-62 Using raw CollapsiblePrimitive instead of local wrapper Pre-existing pattern, not introduced by this PR — migration correctly updated import path
color-picker.tsx:29-44 Using raw Popover primitives instead of styled wrappers Pre-existing pattern, not introduced by this PR — however the naming issue IS new
agents-docs/package.json Still uses separate @radix-ui packages Out of scope — this PR is scoped to agents-manage-ui only
Reviewers (3)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-frontend 3 0 0 0 0 0 3
pr-review-devops 5 0 0 1 0 0 2
pr-review-consistency 7 0 3 0 4 0 0
Total 15 0 3 1 4 0 5

<Popover>
<PopoverTrigger asChild>
<PopoverPrimitive.Root>
<PopoverPrimitive.PopoverTrigger asChild>
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.

🟡 Minor: Incorrect sub-component access pattern

Issue: Uses PopoverPrimitive.PopoverTrigger and PopoverPrimitive.PopoverContent but the unified radix-ui package exports these as Trigger and Content (without the component prefix).

Why: Looking at the migrated popover.tsx wrapper (line 17), the correct pattern is PopoverPrimitive.Trigger and PopoverPrimitive.Content. This file uses the prefixed versions which may break at runtime if the radix-ui package doesn't export these names.

Fix: Update to use the correct sub-component names, or better yet, import the styled wrappers from @/components/ui/popover:

Suggested change
<PopoverPrimitive.PopoverTrigger asChild>
<PopoverPrimitive.Trigger asChild>

Refs:

</PopoverTrigger>
<PopoverContent className="flex flex-row w-fit p-2">
</PopoverPrimitive.PopoverTrigger>
<PopoverPrimitive.PopoverContent className="flex flex-row w-fit p-2">
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.

🟡 Minor: Incorrect sub-component access pattern

Fix: Update to use the correct sub-component name:

Suggested change
<PopoverPrimitive.PopoverContent className="flex flex-row w-fit p-2">
<PopoverPrimitive.Content className="flex flex-row w-fit p-2">

@@ -37,11 +37,11 @@ export function ColorPickerInput({ placeholder, color, setColor }: ColorPickerIn
>
<span className="sr-only">Color picker</span>
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.

🟡 Minor: Incorrect closing tag

Fix: Update closing tag to match:

Suggested change
<span className="sr-only">Color picker</span>
</PopoverPrimitive.Trigger>

<HexColorPicker color={color} onChange={setColor} />
</PopoverContent>
</Popover>
</PopoverPrimitive.PopoverContent>
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.

🟡 Minor: Incorrect closing tag

Fix: Update closing tag to match:

Suggested change
</PopoverPrimitive.PopoverContent>
</PopoverPrimitive.Content>

@@ -1,5 +1,5 @@
import type { InkeepBaseSettings } from '@inkeep/agents-ui/types';
import { TabsContent } from '@radix-ui/react-tabs';
import { Tabs as RadixTabs } from 'radix-ui';
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.

💭 Consider: Inconsistent import naming convention

Issue: Uses Tabs as RadixTabs but the established convention across all UI components is ComponentName as ComponentNamePrimitive (e.g., Tabs as TabsPrimitive in tabs.tsx).

Why: All UI component files use the *Primitive suffix for radix-ui imports. Using RadixTabs here breaks that consistency.

Fix: Either align with the convention:

Suggested change
import { Tabs as RadixTabs } from 'radix-ui';
import { Tabs as TabsPrimitive } from 'radix-ui';

Or better yet, remove this import entirely and use the local styled TabsContent from @/components/ui/tabs (which is already partially imported on line 4) — see related comment below.

Comment on lines +91 to +96
<RadixTabs.TabsContent value="react">
<Streamdown>{reactCode}</Streamdown>
</TabsContent>
<TabsContent value="js">
</RadixTabs.TabsContent>
<RadixTabs.TabsContent value="js">
<Streamdown>{javascriptCode}</Streamdown>
</TabsContent>
</RadixTabs.TabsContent>
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.

💭 Consider: Direct radix-ui usage bypasses local styled wrapper

Issue: Uses RadixTabs.TabsContent directly from radix-ui instead of the local styled TabsContent component, even though Tabs, TabsList, and TabsTrigger are imported from @/components/ui/tabs.

Why: The local TabsContent wrapper includes consistent styling (mt-2 ring-offset-background focus-visible:outline-none...). Using the raw primitive bypasses this styling and creates visual inconsistency.

Fix: Add TabsContent to the existing import on line 4:

import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

Then update usage:

<TabsContent value="react">
  <Streamdown>{reactCode}</Streamdown>
</TabsContent>
<TabsContent value="js">
  <Streamdown>{javascriptCode}</Streamdown>
</TabsContent>

This would also allow removing the radix-ui import on line 2 entirely.

Refs:

import { GripVertical, MoreVertical, Pencil, Trash2 } from 'lucide-react';
import NextLink from 'next/link';
import { useParams } from 'next/navigation';
import type { Checkbox as RadixCheckbox } from 'radix-ui';
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.

💭 Consider: Inconsistent type import naming convention

Issue: Uses Checkbox as RadixCheckbox but the established convention is ComponentName as ComponentNamePrimitive.

Why: All other files use the *Primitive suffix for radix-ui imports (e.g., CheckboxPrimitive in checkbox.tsx). Using RadixCheckbox breaks this naming pattern.

Fix:

Suggested change
import type { Checkbox as RadixCheckbox } from 'radix-ui';
import type { Checkbox as CheckboxPrimitive } from 'radix-ui';

Then update line 88 from RadixCheckbox.CheckedState to CheckboxPrimitive.CheckedState.

Refs:

@github-actions github-actions Bot deleted a comment from claude Bot Mar 25, 2026
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

🟡 Minor (1) 🟡

Inline Comments:

  • 🟡 Minor: chat-ui-code.tsx:4 Mixed import pattern - uses local wrappers for some Tabs components but raw radix-ui for TabsContent

💭 Consider (3) 💭

💭 1) color-picker.tsx:30 Redundant sub-component naming

Issue: Uses PopoverPrimitive.PopoverTrigger and PopoverPrimitive.PopoverContent while popover.tsx uses the shorter PopoverPrimitive.Trigger and PopoverPrimitive.Content.
Why: Both patterns work, but creates internal inconsistency. The radix-ui unified package supports both naming styles.
Fix: Optional - align with the shorter pattern used in popover.tsx for consistency.

💭 2) collapsible.tsx + info-card.tsx Redundant sub-component naming

Issue: Uses CollapsiblePrimitive.CollapsibleTrigger and CollapsiblePrimitive.CollapsibleContent (lines 11-18 in collapsible.tsx, lines 44-59 in info-card.tsx).
Why: Both work correctly. This is a stylistic inconsistency with how other components like popover.tsx access sub-components.
Fix: Optional - could align with shorter pattern CollapsiblePrimitive.Trigger / CollapsiblePrimitive.Content.

💭 3) package.json:109 Alphabetical ordering

Issue: radix-ui is added at the end of the dependencies object rather than in its alphabetical position (between react-runner and recharts).
Why: Minor consistency issue. The codebase appears to use alphabetical ordering for dependencies.
Fix: Move "radix-ui": "^1.4.3" to its alphabetical position.


💡 APPROVE WITH SUGGESTIONS

Summary: This is a clean migration from individual @radix-ui/react-* packages to the unified radix-ui package per the shadcn 2026-02 changelog. CI passes ✅. The changes are mechanical import updates with no functional impact. The minor issues noted are optional polish items that don't block merging. The inline comment about chat-ui-code.tsx is the most actionable item - the file should use the local TabsContent wrapper for styling consistency.

Note: The user also requested a changeset - I'll add that separately.

Discarded (2)
Location Issue Reason Discarded
package.json:109 Caret range ^1.4.3 for radix-ui Follows same pattern as the removed @radix-ui/* packages which also used caret ranges. Consistent with existing practice.
pnpm-lock.yaml Large lockfile changes Expected result of dependency consolidation. No issues identified.
Reviewers (4)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-frontend 1 0 0 0 1 0 0
pr-review-consistency 5 0 2 0 0 0 3
pr-review-devops 3 0 1 0 0 0 2
pr-review-standards 0 0 0 0 0 0 0
Total 9 0 3 0 1 0 5

Note: Multiple reviewers flagged the same chat-ui-code.tsx mixed import issue - consolidated into single inline comment.

import { TabsContent } from '@radix-ui/react-tabs';
import { Tabs as RadixTabs } from 'radix-ui';
import { Streamdown } from 'streamdown';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
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.

🟡 Minor: Use local TabsContent wrapper instead of raw Radix primitive

Issue: This file imports Tabs, TabsList, TabsTrigger from the local wrapper (@/components/ui/tabs) but bypasses it for TabsContent by importing directly from radix-ui. The local TabsContent wrapper (defined in tabs.tsx) includes project-standard styling that the raw primitive doesn't have.

Why: The inconsistency means TabsContent won't have the same styling (mt-2 ring-offset-background focus-visible:outline-none...) applied, creating visual inconsistency.

Fix:

Suggested change
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

Then remove the radix-ui import on line 2 and replace RadixTabs.TabsContent on lines 91/94 with TabsContent.

Refs:

@github-actions github-actions Bot deleted a comment from claude Bot Mar 25, 2026
@itoqa
Copy link
Copy Markdown

itoqa Bot commented Mar 25, 2026

Ito Test Report ✅

18 test cases ran. 18 passed.

All 18 test cases passed in the local environment with zero failures, indicating stable behavior across project management, agent builder, Ship modal, navigation, edge-case overlay handling, and adversarial security scenarios. Key findings were that destructive flows were safe and consistent (idempotent delete under rapid clicks, non-admin deletion blocked, stale-tab deletion reconciled to a recoverable not-found state), UI/UX remained robust (tab synchronization, keyboard/mobile accessibility, Escape/outside-click dismissal, deep-link recovery, single navigation transitions), and security boundaries held (cross-tenant access denied and script/URL injection payloads remained inert).

✅ Passed (18)
Category Summary Screenshot
Adversarial Intro/Placeholder payloads were handled as inert text and did not execute script behavior. ADV-1
Adversarial Malicious avatar URL schemes stayed inert through rerenders with no script execution or navigation takeover. ADV-2
Adversarial Cross-tenant URL tampering was blocked in UI/API with forbidden access and no protected content exposure. ADV-3
Adversarial Multi-tab stale-state after deletion reconciled to a safe Project not found flow with recovery back to projects. ADV-5
Edge Rapid repeated delete clicks produced one consistent outcome: project deleted once and remained absent after refresh. EDGE-1
Edge High-frequency tab switching across top-level and nested tabs preserved interactivity and single-active-tab behavior without lockups. EDGE-2
Edge Refresh and history navigation with an open overlay did not leave stale backdrop/focus artifacts; overlays remained functional. EDGE-3
Edge At 390x844, Ship modal controls remained accessible, overlays were not clipped, and close actions remained reachable. EDGE-4
Edge Invalid sidepane query params recovered to a valid pane, and selecting Sub Agent reopened the sidepane correctly. EDGE-5
Edge Project-switcher dropdown and Ship modal dismiss correctly via outside click/Escape and explicit close without interaction lockups. EDGE-6
Logic Color picker popover opened/closed correctly, accepted a visibly different value, and reflected the updated configuration in the preview context. LOGIC-1
Logic Keyboard-only interactions successfully operated select/color/switch/text controls with visible focus and Escape dismissal behavior. LOGIC-2
Logic Create App in Ship modal opened the project Apps route in a new tab, and sidebar Agents navigation executed once without duplicate navigation effects. LOGIC-3
Happy-path Project overflow menu displayed Edit/Delete actions and closed on Escape with focus returning to trigger. ROUTE-1
Happy-path Created two disposable projects, deleted one via normal confirmation and one via rapid multi-click confirmation, and observed one consistent deletion outcome that persisted after refresh. ROUTE-2
Happy-path Delete denial dialog appeared for non-admin flow and no destructive control was reachable; project remained after refresh. ROUTE-3
Happy-path Sub-agent skill selection flow worked, final unchecked Always loaded state persisted after reload, and invalid deep-link recovery remained usable. ROUTE-4
Happy-path Ship modal opened with Chat UI Preview by default; top-level and nested tab synchronization held under rapid switching, color popover and keyboard interactions worked, and modal closed by Escape and close button including mobile viewport coverage. ROUTE-5

Commit: 2c9ce3b

View Full Run


Tell us how we did: Give Ito Feedback

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) New Issues | Risk: Low

Re-review scope: Changes since last review (2c9ce3bedaf17000c4ee9)

✅ Delta Changes Verified

The polish commit correctly addressed the prior review feedback:

File Issue Fixed Status
chat-ui-code.tsx:2 Naming inconsistency (RadixTabsTabsPrimitive) ✅ Fixed
skill-selector.tsx:4 Naming inconsistency (RadixCheckboxCheckboxPrimitive) ✅ Fixed
color-picker.tsx:30,40-41 Sub-component naming (PopoverTriggerTrigger, etc.) ✅ Fixed

All three files in the delta show proper fixes aligned with the codebase's *Primitive naming convention.

🕐 Pending Recommendations (3)

  • 🟡 package.json:109 Alphabetical ordering — radix-ui placed after zustand, should be between react-runner and recharts
  • 💭 chat-ui-code.tsx:91-96 Optional — still uses raw TabsPrimitive.TabsContent instead of local styled TabsContent wrapper
  • 💭 info-card.tsx:44-59 Optional — uses longer CollapsiblePrimitive.CollapsibleTrigger pattern vs shorter CollapsiblePrimitive.Trigger

✅ APPROVE

Summary: Clean migration from individual @radix-ui/react-* packages to the unified radix-ui meta-package. The polish commit successfully addressed all naming convention issues from the prior review. The remaining pending items are low-severity polish — none are blocking. Ship it! 🚀

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
delta-check 3 0 0 0 0 3 0
Total 3 0 0 0 0 3 0

Note: Re-review scoped to delta changes only. All prior feedback in delta was addressed.

@github-actions github-actions Bot deleted a comment from claude Bot Mar 25, 2026
@vercel vercel Bot temporarily deployed to Preview – agents-docs March 25, 2026 08:16 Inactive
@dimaMachina
Copy link
Copy Markdown
Collaborator Author

@claude add a changeset

@inkeep-internal-ci
Copy link
Copy Markdown
Contributor

inkeep-internal-ci Bot commented Mar 25, 2026

Claude finished @dimaMachina's task in 53s —— View job


Adding changeset

  • Create changeset for agents-manage-ui
  • Commit and push

Added changeset: @inkeep/agents-manage-ui patch — "Migrate from individual @radix-ui/react-* packages to unified radix-ui package"
| Branch

Co-authored-by: Dimitri POSTOLOV <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions github-actions Bot deleted a comment from claude Bot Mar 25, 2026
@dimaMachina dimaMachina added this pull request to the merge queue Mar 25, 2026
Merged via the queue into main with commit 1b37b29 Mar 25, 2026
15 of 16 checks passed
@dimaMachina dimaMachina deleted the unify-radix-components branch March 25, 2026 08:37
@itoqa
Copy link
Copy Markdown

itoqa Bot commented Mar 25, 2026

Ito Test Report ❌

19 test cases ran. 3 failed, 16 passed.

Across 19 end-to-end checks, 16 passed and 3 failed, indicating broad stability in the agent editor and Ship modal flows (including persistence, drag-and-drop ordering, keyboard/mobile/navigation stress, invalid deep-link handling, XSS/malformed-input safety, and the expected GET /health HTTP 204 contract) with one concentrated regression area. The critical issue is a reproducible PR-introduced defect in /default/projects delete interactions where rapid dropdown-to-dialog open/close cycles can leave stale pointer-blocking overlay state after the dialog disappears, causing a non-interactive page and making destructive project actions unreliable (High severity in the main and edge delete-flow cases, with related double-click validation blocked).

❌ Failed (3)
Category Summary Screenshot
Edge ⚠️ Rapid open/close cycles reproduce stale pointer interception behavior, leaving interactions blocked. EDGE-1
Edge 🟠 Double-click delete validation is blocked by the same stale overlay state, so the destructive flow cannot run reliably. EDGE-2
Happy-path ⚠️ Delete dialog flow can leave the page non-interactive after rapid close cycles, preventing reliable completion of the delete path. ROUTE-2
⚠️ Rapid open/close of delete dialog does not leave stale overlay
  • What failed: Repeated rapid cycles leave stale pointer interception and block subsequent clicks; expected behavior is no stale overlay and fully clickable page.
  • Impact: High-frequency user actions can break page interactivity and interrupt normal project operations. This degrades reliability of destructive-action safeguards.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Navigate to /default/projects and pick a disposable project.
    2. Open the project menu and click Delete.
    3. Close the confirmation dialog with Escape or Cancel.
    4. Repeat that open/close cycle rapidly at least five times.
    5. Try clicking project controls again and observe stale pointer interception.
  • Code analysis: The edge case exercises the same dropdown-to-dialog transition repeatedly. The current Radix-based wrappers and open-state handling in the production components make this path susceptible to teardown races under rapid interaction.
  • Why this is likely a bug: The non-interactive state is reproducible through standard UI interactions and aligns with a plausible production-layer cleanup defect rather than a test-only modification.

Relevant code:

agents-manage-ui/src/components/projects/project-item-menu.tsx (lines 21-23)

const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);

agents-manage-ui/src/components/projects/project-item-menu.tsx (lines 60-67)

{isDeleteOpen && (
  <DeleteProjectConfirmation
    projectId={projectData.id}
    projectName={projectName}
    setIsOpen={setIsDeleteOpen}
    isOpen={isDeleteOpen}
  />
)}

agents-manage-ui/src/components/ui/dialog.tsx (lines 35-48)

function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
  return (
    <DialogPrimitive.Overlay
      data-slot="dialog-overlay"
      className={cn(
        'data-[state=open]:animate-in ... fixed inset-0 z-50 bg-black/50 backdrop-blur-xs',
        className
      )}
      {...props}
    />
  );
}
🟠 Double-click delete button does not trigger duplicate destructive flow
  • What failed: The flow cannot be exercised reliably because interaction becomes blocked by stale pointer interception before/while validating the terminal delete behavior.
  • Impact: Users may be unable to complete deletion actions consistently during fast interactions. This blocks confidence in the intended idempotent destructive flow.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Navigate to /default/projects and open a disposable project menu.
    2. Open the Delete confirmation and close it repeatedly in quick succession.
    3. Reopen Delete and attempt the double-click delete action.
    4. Observe that interaction is blocked/intercepted before reliable completion of the idempotency check.
  • Code analysis: This scenario depends on the same menu->dialog path and inherits the overlay teardown issue. The failure mode is consistent with the same production defect in dialog/menu lifecycle handling.
  • Why this is likely a bug: The observed blocking state occurs in the real production delete path and is explained by the same modal-layer lifecycle issue in current UI code, not by test-only patches.

Relevant code:

agents-manage-ui/src/components/projects/project-item-menu.tsx (lines 44-47)

<DropdownMenuItem variant="destructive" onClick={() => setIsDeleteOpen(true)}>
  <Trash2 />
  Delete
</DropdownMenuItem>

agents-manage-ui/src/components/projects/delete-project-confirmation.tsx (lines 79-83)

<DeleteConfirmation
  itemName={itemName}
  isSubmitting={isSubmitting}
  onDelete={handleDelete}
/>

agents-manage-ui/src/components/ui/dialog.tsx (lines 9-11)

function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {
  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
⚠️ Delete project confirmation dialog happy path
  • What failed: The page enters a non-interactive state where pointer interactions are intercepted even though no dialog is visible; expected behavior is full interactivity after dialog close.
  • Impact: Users can get stuck and be unable to continue project actions without a refresh. Destructive/project-management workflows become unreliable.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Navigate to /default/projects.
    2. Open the target project menu and click Delete.
    3. Close the dialog with Escape or Cancel.
    4. Repeat open/close rapidly several times.
    5. Attempt to continue interacting with the page or reopen delete flow and observe pointer interception.
  • Code analysis: I reviewed the project menu trigger path and delete dialog composition. The flow stacks modal primitives (dropdown + dialog) with rapid toggling and relies on implicit teardown; the current implementation can leave pointer-blocking state behind when layers are opened/closed quickly.
  • Why this is likely a bug: The production UI code path combines modal layers and rapid state transitions in a way that plausibly leaks global pointer-blocking state, matching the observed pointer-events lock with no visible dialog.

Relevant code:

agents-manage-ui/src/components/projects/project-item-menu.tsx (lines 26-48)

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="ghost" size="icon-sm" className=" p-0 hover:bg-accent hover:text-accent-foreground rounded-sm -mr-2">
      <MoreVertical className="h-4 w-4 text-muted-foreground" />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end" className="w-48 shadow-lg border border-border bg-popover/95 backdrop-blur-sm">
    <DropdownMenuItem className="cursor-pointer" onClick={() => setIsEditOpen(true)}>
      <Pencil className="size-4" />
      Edit
    </DropdownMenuItem>
    <DropdownMenuItem variant="destructive" onClick={() => setIsDeleteOpen(true)}>
      <Trash2 />
      Delete
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

agents-manage-ui/src/components/projects/delete-project-confirmation.tsx (lines 54-55)

<Dialog open={isOpen} onOpenChange={setIsOpen}>
  {isLoading ? (

agents-manage-ui/src/components/ui/dialog.tsx (lines 93-111)

<DialogPortal data-slot="dialog-portal">
  {size !== 'fullscreen' && <DialogOverlay />}
  <DialogPrimitive.Content
    data-slot="dialog-content"
    className={cn(dialogContentVariants({ variant, position, size, className }), className)}
    {...props}
  >
    {children}
    {showCloseButton && (
      <DialogPrimitive.Close data-slot="dialog-close" className="ring-offset-background ...">
✅ Passed (16)
Category Summary Screenshot
Adversarial Ship modal remained stable under payload injection and payloads stayed inert with no script execution observed. ADV-1
Adversarial Malformed and injection-like color inputs were handled safely without crashes and the modal remained interactive. ADV-2
Adversarial Keyboard stress did not trap focus or freeze overlays, and the Ship modal was usable afterward. ADV-3
Adversarial Under rapid alternating toolbar clicks, UI briefly entered modal/menu overlap states but recovered to a consistent interactive state without permanent lockup. ADV-4
Adversarial Invalid deep-link produced a graceful error/empty state, and normal navigation to a valid skill editor still worked afterward. ADV-5
Edge Mobile ship modal tabs, color popover, and React/Javascript code toggles remained usable and readable at 390x844 after history traversal. EDGE-3
Edge After browser refresh from Chat UI > Code > Javascript, the editor recovered; reopening Ship modal and retesting tabs worked without stale-state crash. EDGE-4
Edge Repeated back/forward traversal across projects and agent editor recovered cleanly with no frozen overlays or blocked controls. EDGE-5
Edge Keyboard-only navigation (Enter/Escape/Tab/Shift+Tab/ArrowDown spam) did not leave overlays stuck and the UI remained interactive. EDGE-6
Logic Code view toggled repeatedly between React and Javascript and consistently rendered the corresponding snippets without panel corruption. LOGIC-1
Logic Primary brand color interactions in Preview were reflected in generated code (e.g., #00ff00), and popover open/close behavior remained stable. LOGIC-2
Logic Always loaded checkbox toggled successfully and persisted after save + page reload on the same sub-agent. LOGIC-3
Logic Drag-and-drop skill reorder updated numbering immediately and persisted after save/reload. LOGIC-4
Happy-path Projects page and menu overlay behavior verified; liveness contract is HTTP 204 by design. ROUTE-1
Happy-path Agent editor route loaded, sub-agent node pane exposed skill controls (Always loaded + ordering), invalid skill deep-link was handled with error state, and normal valid skill edit path still worked. ROUTE-3
Happy-path Ship modal opened with expected top-level tabs; nested Chat UI Preview/Code and React/Javascript panels switched correctly, malicious payloads stayed inert, malformed color inputs did not break interactivity, and refresh/reopen recovered successfully. ROUTE-4

Commit: 76f4798

View Full Run


Tell us how we did: Give Ito Feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant