Skip to content

refactor(deps): all in shadcn-ui#136

Merged
Debbl merged 5 commits into
mainfrom
refactor/all-in-shadcn-ui
May 15, 2026
Merged

refactor(deps): all in shadcn-ui#136
Debbl merged 5 commits into
mainfrom
refactor/all-in-shadcn-ui

Conversation

@Debbl

@Debbl Debbl commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Big redesign of the component library: many new UI primitives and full-featured components (cards, forms, menus, dialogs, sidebar, carousel, calendar, charts, tables, etc.).
    • New tooltip, popover, progress, and interactive controls with improved animations and ripple support.
  • Bug Fixes & Improvements

    • Consistent event/aria handling and keyboard interactions across components.
    • Improved image handling and layout behavior.
  • Styling & Design

    • Updated theme integration, Tailwind styles and dark-mode refinements.
    • Updated global styles and accessibility-focused visual tweaks.

Review Change Stack

Debbl added 4 commits April 27, 2026 15:28
Upgrade HeroUI and related UI dependencies to the new major API surface.
Migrate existing tool pages to HeroUI v3 compound components, update resizable panel orientation props, and adapt animate-ui motion wrappers to current types.
- Added unoptimized images configuration in next.config.ts.
- Replaced inline script with next/script component in _layout.tsx for improved loading strategy.
- Removed dependencies for @heroui/react and @heroui/styles from package.json and pnpm-lock.yaml.
- Added shadcn as a new dependency.
- Updated imports in various components to use shadcn instead of HeroUI components.
- Adjusted ResizablePanel properties for consistency across components.
@netlify

netlify Bot commented Apr 27, 2026

Copy link
Copy Markdown

Deploy Preview for tools-aiwan-run failed.

Name Link
🔨 Latest commit f5e0d16
🔍 Latest deploy log https://app.netlify.com/projects/tools-aiwan-run/deploys/69ef554bc727410008514dc8

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7def042-4c38-4307-8532-b6b732bf356f

📥 Commits

Reviewing files that changed from the base of the PR and between f5e0d16 and e1202ed.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

📝 Walkthrough

Walkthrough

Updates package metadata and performs a large dependency and devDependency overhaul in package.json: bumps pnpm, removes HeroUI/radix packages, adds Base UI and several UI/chart libraries, and upgrades many tooling/runtime versions.

Changes

Configuration & Dependencies

Layer / File(s) Summary
Package manager field
package.json
packageManager updated from pnpm@10.33.0 to pnpm@10.33.4.
Dependencies & devDependencies
package.json
Removed @heroui/*, framer-motion-3d, radix-ui; added @base-ui/react, cmdk, embla-carousel-react, input-otp, react-day-picker, recharts, vaul; bumped many libraries and tooling versions across dependencies and devDependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Debbl/tools.aiwan.run#120 — Modifies the random-name feature including utility functions and page component, directly related to the random-name refactoring in this PR.

Poem

🐰 I nibbled at package.json with care,

Swapped old UI crumbs for a fresh Base fare,
Versions bumped and extras sewn,
New components sprout where seeds were thrown,
A hopping update — tidy and fair.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of this pull request, which is a comprehensive migration from HeroUI and other component libraries to shadcn-ui. The changeset includes mass refactoring of UI components, dependency updates, and configuration changes all centered around adopting shadcn-ui.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/all-in-shadcn-ui

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/(locale)/[locale]/pinyin/page.tsx (1)

21-78: ⚠️ Potential issue | 🟠 Major

aria-invalid checks are i18n-broken and one targets the wrong field.

Two issues:

  1. aria-invalid={inputText === 'Invalid input'} (line 53) compares the user's raw input to the literal sentinel — the input field is only “invalid” if pinyin parsing failed, which is reflected in the output, not in inputText. This effectively never marks the input as invalid (and would falsely mark it if a user typed “Invalid input”).
  2. Both lines 53 and 73 string-compare against the literal 'Invalid input', but pingyinText is set via t\Invalid input`(line 28), which returns a *translated* string at runtime. In any non-English locale these comparisons are alwaysfalse`, silently breaking the invalid state.

Track invalidity with an explicit boolean instead of comparing translated strings.

🐛 Proposed fix
-  const pingyinText = useMemo(() => {
-    let newPingyinText = ''
-    try {
-      newPingyinText = pinyin(inputText, {
-        style: isShowTone ? pinyin.STYLE_TONE : pinyin.STYLE_NORMAL,
-      }).join(' ')
-    } catch {
-      newPingyinText = t`Invalid input`
-    }
-    return newPingyinText
-  }, [inputText, isShowTone, t])
+  const { pingyinText, isInvalid } = useMemo(() => {
+    try {
+      return {
+        pingyinText: pinyin(inputText, {
+          style: isShowTone ? pinyin.STYLE_TONE : pinyin.STYLE_NORMAL,
+        }).join(' '),
+        isInvalid: false,
+      }
+    } catch {
+      return { pingyinText: t`Invalid input`, isInvalid: true }
+    }
+  }, [inputText, isShowTone, t])
@@
-              aria-invalid={inputText === 'Invalid input'}
+              aria-invalid={isInvalid}
@@
-              aria-invalid={pingyinText === 'Invalid input'}
+              aria-invalid={isInvalid}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(locale)/[locale]/pinyin/page.tsx around lines 21 - 78, The pinyin
invalid check must stop comparing translated strings and use an explicit
boolean: add a new state/variable (e.g., pinyinInvalid or isPinyinInvalid) and
set it when the pinyin conversion in the useMemo for pingyinText throws (set
true in catch, false on success); keep the displayed error text as t`Invalid
input` but use the boolean for aria-invalid on the input Textarea (replace
aria-invalid={inputText === 'Invalid input'}) and on the output Textarea
(replace aria-invalid={pingyinText === 'Invalid input'}), and ensure any
handlers that clear input (setInputText) also clear the invalid flag as
appropriate.
🟠 Major comments (9)
src/components/ui/drawer.tsx-40-40 (1)

40-40: ⚠️ Potential issue | 🟠 Major

Overlay fade animations likely won't fire — data-open: / data-closed: don't match vaul's data-state attribute.

In Tailwind v4, the boolean data-attribute variants data-open: and data-closed: compile to attribute-presence selectors [data-open] / [data-closed]. However, vaul's Drawer.Overlay toggles its visibility via data-state="open" / data-state="closed" (not a data-open boolean attribute). As a result, animate-in fade-in-0 and animate-out fade-out-0 will never apply, and the overlay won't fade in/out as intended. This also diverges from upstream shadcn-ui's drawer, which uses data-[state=open]: / data-[state=closed]:.

🐛 Proposed fix
       data-slot="drawer-overlay"
       className={cn(
-        "fixed inset-0 z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
+        "fixed inset-0 z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
         className
       )}

If this project defines a custom Tailwind variant mapping data-open[data-state=open] (or vaul has been customized to emit data-open/data-closed), this is a non-issue. Please confirm with:

#!/bin/bash
# 1) Look for custom `@custom-variant` / `@variant` definitions in CSS for data-open / data-closed
rg -nP -C2 '@(custom-)?variant\s+data-(open|closed)\b' --type=css

# 2) Confirm what data-* attributes vaul renders on Overlay/Content in the version in use
fd -t f 'package.json' -x grep -lE '"vaul"\s*:' {} \; | head -n 5
rg -nP -C2 '"vaul"\s*:' package.json pnpm-lock.yaml yarn.lock 2>/dev/null | head -n 50

# 3) Cross-check usages elsewhere in this PR's UI primitives for the same pattern
rg -nP --type=tsx -C1 '\bdata-(open|closed):'
In Tailwind CSS v4, does the `data-open:` variant compile to `[data-open]` (attribute presence) or to `[data-state=open]`? And what data-state attribute does `vaul` 1.1.2's Drawer.Overlay set when open/closed?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/drawer.tsx` at line 40, The overlay's Tailwind variants use
data-open:/data-closed: which target boolean attributes and won't match vaul's
data-state="open"/"closed"; update the class string on Drawer.Overlay (the
quoted class value in src/components/ui/drawer.tsx) to use
data-[state=open]:animate-in data-[state=open]:fade-in-0 and
data-[state=closed]:animate-out data-[state=closed]:fade-out-0 (i.e., replace
data-open:/data-closed: with data-[state=open]:/data-[state=closed]:) so the
fade animations apply when vaul toggles data-state, and re-run the build to
verify Tailwind compiles these arbitrary attribute variants.
src/components/ui/slider.tsx-13-17 (1)

13-17: ⚠️ Potential issue | 🟠 Major

Handle scalar value/defaultValue props to match component's type signature.

The component accepts SliderPrimitive.Root.Props, which includes number | number[] for value and defaultValue. However, the _values calculation (lines 13–17) only checks Array.isArray(), silently ignoring scalar numbers. If a developer passes defaultValue={50}, it falls through to the [min, max] fallback, rendering two thumbs instead of one.

Additionally, when neither value nor defaultValue is provided, defaulting to [min, max] creates a two-thumb slider by default, which may not match single-value use cases.

Wrap scalar values as arrays and consider defaulting to [min] for single-thumb sliders:

Suggested fix
-  const _values = Array.isArray(value)
-    ? value
-    : Array.isArray(defaultValue)
-      ? defaultValue
-      : [min, max]
+  const _values = Array.isArray(value)
+    ? value
+    : Array.isArray(defaultValue)
+      ? defaultValue
+      : typeof value === "number"
+        ? [value]
+        : typeof defaultValue === "number"
+          ? [defaultValue]
+          : [min]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/slider.tsx` around lines 13 - 17, _value computation
currently ignores scalar number props causing unintended two-thumb sliders;
update the logic that builds _values (used by the Slider component) to: treat a
numeric value or defaultValue as a single-thumb by wrapping it into an array
(e.g., if typeof value === 'number' then _values = [value]), preserve arrays
as-is, and when neither value nor defaultValue is provided default to a
single-thumb baseline (e.g., [min]) instead of [min, max]; reference the _values
variable and the value/defaultValue props from SliderPrimitive.Root.Props and
ensure min/max remain available for bounds.
src/components/ui/field.tsx-118-129 (1)

118-129: ⚠️ Potential issue | 🟠 Major

FieldTitle uses the wrong data-slot value.

FieldTitle is set to data-slot="field-label", which collides with FieldLabel (line 107). Any CSS selector or has-[[data-slot=field-label]]: rule (and there are several in this file) will match titles too, leading to incorrect styling.

🐛 Proposed fix
     <div
-      data-slot="field-label"
+      data-slot="field-title"
       className={cn(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/field.tsx` around lines 118 - 129, FieldTitle is using the
same data-slot value as FieldLabel causing selector collisions; update the
FieldTitle component (function FieldTitle) to use a distinct data-slot value
such as "field-title" instead of "field-label", and then scan this file for any
CSS/has-[[data-slot=field-label]] rules that should target labels vs titles and
adjust selectors accordingly so only FieldLabel matches "field-label" and
FieldTitle matches "field-title".
src/components/animate-ui/primitives/buttons/ripple.tsx-43-65 (1)

43-65: ⚠️ Potential issue | 🟠 Major

Ripple id collisions on rapid clicks, and the cleanup timer leaks past unmount.

Two concrete issues:

  1. id: Date.now() is not unique within the same millisecond — fast double clicks (or replayed taps) produce identical keys, causing React key warnings and the filter on line 61 deletes both ripples at once.
  2. The setTimeout is never cleared. If the component unmounts before 600 ms elapse, setRipples runs on an unmounted instance.
🐛 Proposed fix
+  const timeoutsRef = React.useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
+
+  React.useEffect(() => {
+    return () => {
+      timeoutsRef.current.forEach(clearTimeout);
+      timeoutsRef.current.clear();
+    };
+  }, []);
+
   const createRipple = React.useCallback(
     (event: React.MouseEvent<HTMLButtonElement>) => {
       const button = buttonRef.current;
       if (!button) return;

       const rect = button.getBoundingClientRect();
       const x = event.clientX - rect.left;
       const y = event.clientY - rect.top;

       const newRipple: Ripple = {
-        id: Date.now(),
+        // Combine timestamp with a random suffix to avoid collisions on rapid clicks.
+        id: Date.now() + Math.random(),
         x,
         y,
       };

       setRipples((prev) => [...prev, newRipple]);

-      setTimeout(() => {
+      const timeout = setTimeout(() => {
         setRipples((prev) => prev.filter((r) => r.id !== newRipple.id));
+        timeoutsRef.current.delete(timeout);
       }, 600);
+      timeoutsRef.current.add(timeout);
     },
     [],
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/animate-ui/primitives/buttons/ripple.tsx` around lines 43 -
65, The ripple ids generated in createRipple using Date.now() can collide and
the setTimeout used to remove ripples is never cleared on unmount; replace the
id generation with a truly unique id (e.g., a per-component counter ref or
crypto.randomUUID()) so each newRipple.id is unique, and capture timeout handles
(store them in a ref like rippleTimeoutsRef) when calling setTimeout so you can
clear them on unmount; add a useEffect cleanup that iterates rippleTimeoutsRef
to clearTimeout every pending timer and optionally clear the ref, and update
createRipple to push the timeout handle into that ref when scheduling the
removal for setRipples.
src/components/animate-ui/primitives/animate/slot.tsx-48-69 (1)

48-69: ⚠️ Potential issue | 🟠 Major

Event handlers from the child are silently overwritten.

mergeProps does { ...childProps, ...slotProps }, so any handler defined on the consumer's child element (e.g. onClick, onPointerDown) is replaced by the slot's handler rather than chained. This diverges from the Radix/shadcn Slot contract and affects RippleButton where the slot forwards onClick={handleClick} — any user-supplied onClick on the child element would be lost.

Consider chaining functions for known event props (or any prop starting with on whose value is a function), preserving className/style merge as-is.

♻️ Suggested approach
 function mergeProps<T extends HTMLElement>(
   childProps: AnyProps,
   slotProps: DOMMotionProps<T>,
 ): AnyProps {
   const merged: AnyProps = { ...childProps, ...slotProps };

+  // Chain event handlers (props starting with "on") so child handlers still fire.
+  for (const key of Object.keys(slotProps)) {
+    const slotValue = (slotProps as AnyProps)[key];
+    const childValue = childProps[key];
+    if (
+      key.startsWith("on") &&
+      typeof slotValue === "function" &&
+      typeof childValue === "function"
+    ) {
+      merged[key] = (...args: unknown[]) => {
+        (childValue as (...a: unknown[]) => unknown)(...args);
+        (slotValue as (...a: unknown[]) => unknown)(...args);
+      };
+    }
+  }
+
   if (childProps.className || slotProps.className) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/animate-ui/primitives/animate/slot.tsx` around lines 48 - 69,
mergeProps currently spreads slotProps over childProps which silently overwrites
event handlers; update mergeProps (the function named mergeProps<T extends
HTMLElement>) to detect prop keys starting with "on" whose values are functions
in both childProps and slotProps and replace the simple overwrite with a chained
function that calls both handlers (preserve event object and call child handler
first then slot handler, or otherwise maintain existing order used by
Radix/shadcn), while keeping the existing className merging via cn and style
merging logic intact; ensure you handle cases where only one side has the
handler and preserve typings for DOMMotionProps<T>/AnyProps.
src/components/ui/button-group.tsx-1-7 (1)

1-7: ⚠️ Potential issue | 🟠 Major

Missing "use client" directive.

ButtonGroupText calls useRender(...) from @base-ui/react/use-render, which is a React hook and uses hooks internally. In Next.js App Router this file must be a Client Component, otherwise rendering it from a Server Component will throw at runtime. Every other base-ui-consuming file added in this PR (avatar.tsx, context-menu.tsx, hover-card.tsx, menubar.tsx, tabs.tsx) declares "use client".

🛠 Proposed fix
+"use client"
+
 import { mergeProps } from "@base-ui/react/merge-props"
 import { useRender } from "@base-ui/react/use-render"
 import { cva, type VariantProps } from "class-variance-authority"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/button-group.tsx` around lines 1 - 7, This file is missing
the "use client" directive so the React hook useRender used in ButtonGroupText
will break when rendered from a Server Component; add the top-of-file directive
"use client" (a single line string) before any imports in
src/components/ui/button-group.tsx to mark this as a Client Component so
ButtonGroupText and its useRender hook can run safely.
src/components/show-case.tsx-110-142 (1)

110-142: ⚠️ Potential issue | 🟠 Major

SSR/SEO regression: actual card content is no longer present in server-rendered HTML.

useHydrated returns false during SSR and the initial client render, so the server-rendered output now contains only Skeleton placeholders — the tool titles, descriptions, and links are gone from the initial HTML. The previous Skeleton isLoaded wrapper rendered the real content underneath the skeleton, so it was indexable and visible to no-JS users.

If the goal of the gating is just to avoid hydration mismatches from animation state, consider rendering the card content unconditionally and only deferring the motion animations / dynamic bits, or accepting the brief motion render on the server. As-is, the showcase grid is not crawlable by no-JS clients.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/show-case.tsx` around lines 110 - 142, The server render
currently shows only Skeletons because useHydrated gating in the ShowCase
component hides the real card markup; change the logic so the actual card
content (CardHeader, CardContent, Link, img, titles/descriptions) is rendered
unconditionally on server and client, and only gate animation/dynamic behavior
(e.g., motion components or animation classes tied to useHydrated) behind
useHydrated; locate the conditional around isHydrated in
src/components/show-case.tsx and replace it so Skeletons are used as
overlays/placeholders or for client-only animated transitions while the real
content (item.title, item.description, item.href, item.icon,
CardHeader/CardContent) remains in the DOM for SSR/SEO.
src/hooks/use-controlled-state.tsx-8-31 (1)

8-31: ⚠️ Potential issue | 🟠 Major

Fix type safety and replace effect-based sync with derived state.

Two related issues:

  1. Type safety violation: Rest extends any[] at line 8 uses any[]. Replace with unknown[] (rest-tuple constraint) to maintain type safety—call sites that pass extra args remain typed.

  2. useEffect anti-pattern: Syncing value into local state via useEffect (lines 19–21) causes an extra render on every controlled prop change and is fragile when transitioning between controlled and uncontrolled modes. The idiomatic React approach is to derive the rendered value during render: use state = value !== undefined ? value : internalState and only update internalState when uncontrolled. This eliminates the extra commit and stale state risks.

Proposed refactor
export function useControlledState<T, Rest extends any[] = []>(
+export function useControlledState<T, Rest extends unknown[] = []>(
   props: CommonControlledStateProps<T> & {
     onChange?: (value: T, ...args: Rest) => void
   },
 ): readonly [T, (next: T, ...args: Rest) => void] {
   const { value, defaultValue, onChange } = props

-  const [state, setInternalState] = React.useState<T>(
+  const [internalState, setInternalState] = React.useState<T>(
     value !== undefined ? value : (defaultValue as T),
   )
-
-  React.useEffect(() => {
-    if (value !== undefined) setInternalState(value)
-  }, [value])
+  // Derive state during render; prefer controlled prop when provided.
+  const isControlled = value !== undefined
+  const state = isControlled ? (value as T) : internalState

   const setState = React.useCallback(
     (next: T, ...args: Rest) => {
-      setInternalState(next)
+      if (!isControlled) setInternalState(next)
       onChange?.(next, ...args)
     },
-    [onChange],
+    [isControlled, onChange],
   )

   return [state, setState] as const
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/use-controlled-state.tsx` around lines 8 - 31, In
useControlledState, replace the unsafe Rest extends any[] with Rest extends
unknown[] to preserve tuple typing; remove the useEffect sync of value into
internal state and instead compute the exposed state as const state = value !==
undefined ? value : internalState during render, and only call setInternalState
when the component is uncontrolled (i.e., when value is undefined) inside the
setState callback (keep calling onChange(next, ...args) regardless); update
references to value, defaultValue, internalState, setInternalState, setState and
onChange in useControlledState accordingly to ensure no extra effect-driven
renders and correct types.
src/components/ui/sidebar.tsx-484-484 (1)

484-484: ⚠️ Potential issue | 🟠 Major

Remove hsl() wrapper from --sidebar-border and --sidebar-accent variables.

Line 484 wraps full OKLCH color values with hsl(var(...)), producing invalid CSS syntax like hsl(oklch(...)). The shadow will silently fail to render.

Your globals.css defines --sidebar-border and --sidebar-accent as complete color values (e.g., oklch(0.92 0.004 286.32)), not bare HSL channels. Use var(--sidebar-border) and var(--sidebar-accent) directly instead:

Line 484 before and after
- "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sidebar.tsx` at line 484, The CSS class string in the
sidebar component is wrapping CSS custom properties with hsl(), producing
invalid output like hsl(oklch(...)); update the class value used in the Sidebar
component (the array/string that contains "bg-background
shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent
hover:text-sidebar-accent-foreground
hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]") to remove the hsl(...)
wrappers and use var(--sidebar-border) and var(--sidebar-accent) directly so
shadows become "shadow-[0_0_0_1px_var(--sidebar-border)]" and
"hover:shadow-[0_0_0_1px_var(--sidebar-accent)]".
🟡 Minor comments (14)
src/components/ui/navigation-menu.tsx-116-116 (1)

116-116: ⚠️ Potential issue | 🟡 Minor

easing-[ease] is not a Tailwind utility.

The transition-timing-function utility is ease-* (e.g. ease-[ease] / ease-linear). easing-[ease] will be emitted as an unknown class and stripped. Either drop it (the ease-[cubic-bezier(0.22,1,0.36,1)] later in the same string already sets the timing function) or replace with ease-[ease].

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/navigation-menu.tsx` at line 116, The class string on
NavigationMenuPrimitive.Popup contains a non-existent Tailwind utility
"easing-[ease]" which will be stripped; update the className on
NavigationMenuPrimitive.Popup to either remove "easing-[ease]" entirely (since
you already have ease-[cubic-bezier(0.22,1,0.36,1)]) or replace it with the
correct Tailwind utility "ease-[ease]" so the timing function is applied
correctly.
src/components/ui/kbd.tsx-16-24 (1)

16-24: ⚠️ Potential issue | 🟡 Minor

KbdGroup props type doesn't match the rendered element.

KbdGroup is typed as React.ComponentProps<"div"> but renders a <kbd> element. Either change the rendered element to <div> or change the props type to React.ComponentProps<"kbd"> so the type contract matches what consumers can actually pass.

🛠️ Suggested fix
-function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
+function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) {
   return (
     <kbd
       data-slot="kbd-group"
       className={cn("inline-flex items-center gap-1", className)}
       {...props}
     />
   )
 }

As per coding guidelines: "Ensure implementations are type-safe (no any), use readable variable/function names".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/kbd.tsx` around lines 16 - 24, KbdGroup is typed as
React.ComponentProps<"div"> but renders a <kbd>, creating a type mismatch;
update the props type on the KbdGroup component to React.ComponentProps<"kbd">
(or alternatively change the rendered element to a <div>) so the declared prop
type matches the actual rendered element, and ensure the component signature
(KbdGroup) and its usage still accept className and spread props correctly.
src/components/ui/sonner.tsx-39-43 (1)

39-43: ⚠️ Potential issue | 🟡 Minor

Define the cn-toast CSS class in globals.css or replace with an existing style.

The toast: "cn-toast" class at line 41 is not defined anywhere in the codebase. Either add a .cn-toast rule to globals.css, or use an existing Tailwind class instead. Without the definition, this className has no effect on toast styling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sonner.tsx` around lines 39 - 43, The toast configuration
in src/components/ui/sonner.tsx sets toastOptions.classNames.toast = "cn-toast",
but that CSS class isn't defined; either add a .cn-toast rule to your global
stylesheet (e.g., globals.css) with the desired styles for the toast, or replace
"cn-toast" with an existing Tailwind utility/class (or component class) used
elsewhere; update the export in sonner.tsx to reference the chosen class name
and ensure the CSS entry targets the toast element (search for toastOptions,
classNames, and "cn-toast" to locate the code to change).
src/components/ui/carousel.tsx-96-105 (1)

96-105: ⚠️ Potential issue | 🟡 Minor

Detach the reInit listener in cleanup.

The effect subscribes to both reInit and select, but only select is removed on cleanup. If the effect re-runs (e.g., a new onSelect reference) the prior reInit handler stays attached to the same Embla api, accumulating listeners.

♻️ Proposed fix
     return () => {
       api?.off("select", onSelect)
+      api?.off("reInit", onSelect)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/carousel.tsx` around lines 96 - 105, The effect attaches
both the "reInit" and "select" listeners to the Embla `api` but only removes
"select" on cleanup, causing leaked handlers when `onSelect` or `api` change;
update the cleanup to remove both listeners (call api.off("reInit", onSelect)
and api.off("select", onSelect)) so that `useEffect` which references `api` and
`onSelect` properly detaches both handlers (refer to the existing
React.useEffect, `api`, and `onSelect` symbols).
src/components/animate-ui/components/buttons/ripple.tsx-27-42 (1)

27-42: ⚠️ Potential issue | 🟡 Minor

variant may be undefined, and the keyof cast hides missing entries.

When the consumer omits variant, buttonVariants falls back to its 'default' defaultVariant for styling, but rippleButtonVariants[undefined as never] returns undefined, so the --ripple-button-ripple-color CSS variable is never set and the ripple uses the fallback (or stays unstyled). Additionally, variant as keyof typeof rippleButtonVariants will type-check even if a new variant is added to buttonVariants without a matching ripple-color entry, leading to silent regressions.

♻️ Proposed fix
 function RippleButton({
   className,
-  variant,
+  variant = 'default',
   size,
   ...props
 }: RippleButtonProps) {
   return (
     <RippleButtonPrimitive
       className={cn(
         buttonVariants({ variant, size, className }),
-        rippleButtonVariants[variant as keyof typeof rippleButtonVariants],
+        variant ? rippleButtonVariants[variant] : rippleButtonVariants.default,
       )}
       {...props}
     />
   );
 }

Better still, type rippleButtonVariants as Record<NonNullable<VariantProps<typeof buttonVariants>['variant']>, string> so the compiler enforces parity with buttonVariants.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/animate-ui/components/buttons/ripple.tsx` around lines 27 -
42, The ripple color lookup can be undefined because RippleButton reads
rippleButtonVariants[variant as keyof ...] while variant may be undefined;
update RippleButton to guard/derive the effectiveVariant (use the same default
as buttonVariants when variant is missing) and use that to index
rippleButtonVariants instead of casting, and change the rippleButtonVariants
type to enforce parity with buttonVariants by declaring it as
Record<NonNullable<VariantProps<typeof buttonVariants>['variant']>, string> so
the compiler forces matching keys and prevents silent regressions (adjust any
code that constructs rippleButtonVariants to satisfy the new type).
src/components/ui/empty.tsx-71-82 (1)

71-82: ⚠️ Potential issue | 🟡 Minor

EmptyDescription typed as <p> but renders <div>.

The prop type is React.ComponentProps<"p"> (so ref is typed Ref<HTMLParagraphElement> and props are paragraph-flavored), but the rendered element is a <div>. This is a type/runtime mismatch — refs will be wrong, and a description is semantically a paragraph anyway.

🛠 Proposed fix (render a `

`)

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
   return (
-    <div
+    <p
       data-slot="empty-description"
       className={cn(
         "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
         className
       )}
       {...props}
     />
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/empty.tsx` around lines 71 - 82, EmptyDescription is typed
as React.ComponentProps<"p"> but currently returns a <div>, causing mismatched
ref/prop types and semantics; update the component to render a <p> element
instead of a <div> (keep the data-slot="empty-description", preserve the
cn(className...) value and spread {...props}) so the HTML element matches the
declared React.ComponentProps<"p"> and paragraph semantics remain correct.
src/components/ui/context-menu.tsx-142-153 (1)

142-153: ⚠️ Potential issue | 🟡 Minor

className is dropped when a consumer passes one to ContextMenuSubContent.

className is not destructured here, so when a consumer passes one, the trailing {...props} spread overrides the literal className="shadow-lg" and the submenu loses its distinguishing shadow. The sibling MenubarSubContent (src/components/ui/menubar.tsx lines 250–261) correctly merges via cn().

🛠 Proposed fix
 function ContextMenuSubContent({
+  className,
   ...props
 }: React.ComponentProps<typeof ContextMenuContent>) {
   return (
     <ContextMenuContent
       data-slot="context-menu-sub-content"
-      className="shadow-lg"
+      className={cn("shadow-lg", className)}
       side="right"
       {...props}
     />
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/context-menu.tsx` around lines 142 - 153,
ContextMenuSubContent currently drops a consumer-provided className because
props are spread after the literal className; fix by destructuring className
from React.ComponentProps<typeof ContextMenuContent> in the
ContextMenuSubContent signature, import/use the shared cn() utility (same
pattern as MenubarSubContent), and pass className={cn("shadow-lg", className)}
into the ContextMenuContent so consumer classes are merged rather than
overriding the shadow.
src/components/ui/alert-dialog.tsx-144-155 (1)

144-155: ⚠️ Potential issue | 🟡 Minor

AlertDialogAction does not auto-close the dialog on click.

AlertDialogAction renders a plain Button with no AlertDialogPrimitive.Close wrapper, preventing automatic dialog dismissal. In contrast, AlertDialogCancel wraps its button with AlertDialogPrimitive.Close, enabling auto-close behavior. Users will expect the action button to close the dialog by default (standard behavior in @base-ui/react), requiring them to manually add onClick handlers or wrap the component themselves.

Wrap AlertDialogAction with AlertDialogPrimitive.Close for consistency:

♻️ Suggested change
-function AlertDialogAction({
-  className,
-  ...props
-}: React.ComponentProps<typeof Button>) {
-  return (
-    <Button
-      data-slot="alert-dialog-action"
-      className={cn(className)}
-      {...props}
-    />
-  )
-}
+function AlertDialogAction({
+  className,
+  variant,
+  size,
+  ...props
+}: AlertDialogPrimitive.Close.Props &
+  Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
+  return (
+    <AlertDialogPrimitive.Close
+      data-slot="alert-dialog-action"
+      className={cn(className)}
+      render={<Button variant={variant} size={size} />}
+      {...props}
+    />
+  )
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/alert-dialog.tsx` around lines 144 - 155, AlertDialogAction
currently returns a raw Button which does not auto-close the dialog; wrap the
Button return value in AlertDialogPrimitive.Close (same pattern used by
AlertDialogCancel) so clicking the action will dismiss the dialog by default,
preserving className, data-slot and all passed props from the AlertDialogAction
component.
eslint.config.js-6-8 (1)

6-8: ⚠️ Potential issue | 🟡 Minor

Ensure directory patterns recursively ignore nested files.

ESLint's glob patterns require /** suffix to match all nested files within a directory. Bare paths like src/components/ui will not recursively ignore files inside src/components/ui/**.

Suggested fix
-    ignores: {
-      files: ['src/components/ui', 'src/components/animate-ui'],
+    ignores: {
+      files: ['src/components/ui/**', 'src/components/animate-ui/**'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.js` around lines 6 - 8, The ignore patterns in the
ignores.files array are not recursive; update the patterns used in the ignores
object (the files array) to include a recursive glob (e.g., append /** or /**/*
to each directory entry) so ESLint will ignore all nested files under those
directories; modify the values currently 'src/components/ui' and
'src/components/animate-ui' in the ignores.files array accordingly.
src/app/(locale)/[locale]/random-name/page.tsx-56-66 (1)

56-66: ⚠️ Potential issue | 🟡 Minor

Result keys can collide and trigger React duplicate-key warnings.

key: \${parts.adjective}-${parts.hero}`` is not guaranteed unique. With up to 20 generated names from a finite adjective/hero pool, duplicate combinations are realistic and will cause React to warn and reuse DOM nodes incorrectly (state may bleed across rows on regenerate). Either include the index in the key, or attach a stable id at generation time.

♻️ Suggested fix
   const resultItems = useMemo(
     () =>
-      results.map((parts) => ({
-        key: `${parts.adjective}-${parts.hero}`,
+      results.map((parts, index) => ({
+        key: `${index}-${parts.adjective}-${parts.hero}`,
         text: formatRandomName(parts, {
           prefix,
           separator,
         }),
       })),
     [results, prefix, separator],
   )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(locale)/[locale]/random-name/page.tsx around lines 56 - 66, The
computed list in resultItems (inside the useMemo that maps results) uses a
non-unique key `${parts.adjective}-${parts.hero}` which can collide; change the
key to be stable and unique by either including the map index or a stable id
generated when names are created (e.g., attach id at generation time to each
result object and use that id here), so update the mapping in the useMemo to use
that unique identifier (reference: resultItems, useMemo, results,
formatRandomName).
src/components/animate-ui/components/base/tooltip.tsx-41-71 (1)

41-71: ⚠️ Potential issue | 🟡 Minor

TooltipPanelProps unions Positioner + Popup props but {...props} only reaches the Positioner.

TooltipPanelProps = TooltipPositionerPrimitiveProps & TooltipPopupPrimitiveProps, yet after destructuring className/style/sideOffset/children, the rest is forwarded only to TooltipPositionerPrimitive. Any caller passing popup-specific props (event handlers, id, motion props, etc.) will silently be applied to the positioner instead of the popup. Either restrict the type to the positioner and accept popup overrides through a dedicated prop, or split callers' props between the two primitives.

♻️ Sketch of a split
-type TooltipPanelProps = TooltipPositionerPrimitiveProps &
-  TooltipPopupPrimitiveProps;
+type TooltipPanelProps = TooltipPositionerPrimitiveProps & {
+  popupProps?: TooltipPopupPrimitiveProps;
+};
@@
-function TooltipPanel({
-  className,
-  sideOffset = 4,
-  children,
-  style,
-  ...props
-}: TooltipPanelProps) {
+function TooltipPanel({
+  className,
+  sideOffset = 4,
+  children,
+  style,
+  popupProps,
+  ...positionerProps
+}: TooltipPanelProps) {
   return (
     <TooltipPortalPrimitive>
       <TooltipPositionerPrimitive
         sideOffset={sideOffset}
         className="z-50"
-        {...props}
+        {...positionerProps}
       >
         <TooltipPopupPrimitive
           className={cn(
             'bg-primary text-primary-foreground w-fit origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
             className,
           )}
           style={style}
+          {...popupProps}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/animate-ui/components/base/tooltip.tsx` around lines 41 - 71,
TooltipPanel currently types props as TooltipPositionerPrimitiveProps &
TooltipPopupPrimitiveProps but spreads the remaining props only onto
TooltipPositionerPrimitive, causing popup-specific props to be misapplied;
change TooltipPanel to destructure and separate positionerProps and popupProps
(e.g., const { className, style, sideOffset = 4, children, ...rest } then split
rest into positionerProps and popupProps based on known keys or by accepting
explicit positionerProps and popupProps props) and forward positionerProps to
TooltipPositionerPrimitive and popupProps to TooltipPopupPrimitive (ensuring
className and style go to TooltipPopupPrimitive and sideOffset goes to
TooltipPositionerPrimitive) so event handlers, id, motion props, etc. intended
for the popup are applied to TooltipPopupPrimitive rather than the positioner.
src/components/ui/chart.tsx-94-113 (1)

94-113: ⚠️ Potential issue | 🟡 Minor

Treat config[*].color / config[*].theme[*] as trusted; consider basic validation.

ChartStyle interpolates color directly into a <style> block via dangerouslySetInnerHTML. Static analysis flagged it as XSS — in practice values are dev-supplied via ChartConfig, so this is low risk, but a stray string like red; } body { display:none } /* would break out of the declaration since there is no escaping. If chart configs are ever sourced from upstream/user-controlled data (e.g. CMS, query params), this becomes exploitable.

Cheap mitigations: validate against a CSS color regex / CSS.supports('color', value) before emitting, or move to inline style props per series and let React escape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/chart.tsx` around lines 94 - 113, The inline style
injection builds CSS using values from colorConfig and inserts them via
dangerouslySetInnerHTML (see the style element that maps THEMES, id, and
colorConfig) which can be broken out by untrusted strings; validate each color
before interpolating (use CSS.supports('color', value) or a strict CSS color
regex) and skip or replace invalid entries with a safe fallback (e.g., omit the
--color- key or use a defaultColor) when constructing the map output. Update the
code that maps over colorConfig (the anonymous mapping inside the style's
__html) to call a small validator function (e.g., isValidCssColor) and only emit
lines for colors that pass, or sanitize/encode them before concatenation. Ensure
references in the fix point to THEMES, id, colorConfig and the
style/dangerouslySetInnerHTML block so the change is localized and prevents XSS
if configs become untrusted.
src/components/animate-ui/primitives/base/switch.tsx-107-107 (1)

107-107: ⚠️ Potential issue | 🟡 Minor

Typo: whileTap="tab" should be "tap".

The root Switch uses whileTap="tap" (line 70); the thumb uses "tab", which doesn't match any variant label. It's effectively a no-op today (no variants map is defined on the thumb), but the inconsistency is misleading and will silently skip the press animation if variants are later attached.

✏️ Proposed fix
-          whileTap="tab"
+          whileTap="tap"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/animate-ui/primitives/base/switch.tsx` at line 107, The thumb
element inside the Switch component has a typo: it uses whileTap="tab" which
doesn't match the variant name used by the root Switch; update the thumb's prop
from "tab" to "tap" so it matches the root's whileTap="tap" and will trigger the
intended press animation (look for the Switch component and the thumb element
inside it to apply this change).
src/components/ui/sidebar.tsx-292-292 (1)

292-292: ⚠️ Potential issue | 🟡 Minor

Likely RTL bug: ltr:-translate-x-1/2 rtl:-translate-x-1/2 apply the same shift in both directions.

Both modifiers translate by -50%. For a rail anchored on the opposite side under RTL, the offset typically needs to invert (rtl:translate-x-1/2). Worth double-checking against the visual in RTL mode — otherwise the rail will overlap the sidebar instead of sitting next to it.

🔁 Probable fix
-        "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
+        "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:translate-x-1/2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sidebar.tsx` at line 292, The utility classes on the
sidebar rail use identical negative X translations for both directions (the
class string containing "ltr:-translate-x-1/2 rtl:-translate-x-1/2"), which
causes the rail to overlap in RTL; update the RTL modifier to invert the shift
(use "rtl:translate-x-1/2" instead of "rtl:-translate-x-1/2") in the sidebar
component where that class string is defined so the rail is offset outward in
RTL layouts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef6bf3a0-152e-4637-ba8d-3a2aef88e033

📥 Commits

Reviewing files that changed from the base of the PR and between ce0fd7e and f5e0d16.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (97)
  • .vscode/settings.json
  • components.json
  • eslint.config.js
  • next.config.ts
  • package.json
  • src/app/(locale)/[locale]/base64-to-png/page.tsx
  • src/app/(locale)/[locale]/base64/page.tsx
  • src/app/(locale)/[locale]/battery/page.tsx
  • src/app/(locale)/[locale]/code-diff/components/operator.tsx
  • src/app/(locale)/[locale]/generate-query/page.tsx
  • src/app/(locale)/[locale]/keep-screen-on/page.tsx
  • src/app/(locale)/[locale]/miwifi-sn/page.tsx
  • src/app/(locale)/[locale]/pinyin/page.tsx
  • src/app/(locale)/[locale]/random-name/page.tsx
  • src/app/(locale)/[locale]/vscode-extension-download/page.tsx
  • src/app/_layout.tsx
  • src/app/globals.css
  • src/components/animate-ui/buttons/ripple.tsx
  • src/components/animate-ui/components/base/switch.tsx
  • src/components/animate-ui/components/base/tooltip.tsx
  • src/components/animate-ui/components/buttons/button.tsx
  • src/components/animate-ui/components/buttons/ripple.tsx
  • src/components/animate-ui/primitives/animate/slot.tsx
  • src/components/animate-ui/primitives/base/switch.tsx
  • src/components/animate-ui/primitives/base/tooltip.tsx
  • src/components/animate-ui/primitives/buttons/button.tsx
  • src/components/animate-ui/primitives/buttons/ripple.tsx
  • src/components/animate-ui/radix/switch.tsx
  • src/components/animate-ui/radix/tooltip.tsx
  • src/components/footer.tsx
  • src/components/header.tsx
  • src/components/show-case.tsx
  • src/components/theme-switcher.tsx
  • src/components/ui/accordion.tsx
  • src/components/ui/alert-dialog.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/aspect-ratio.tsx
  • src/components/ui/avatar.tsx
  • src/components/ui/badge.tsx
  • src/components/ui/breadcrumb.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/calendar.tsx
  • src/components/ui/card.tsx
  • src/components/ui/carousel.tsx
  • src/components/ui/chart.tsx
  • src/components/ui/checkbox.tsx
  • src/components/ui/collapsible.tsx
  • src/components/ui/combobox.tsx
  • src/components/ui/command.tsx
  • src/components/ui/context-menu.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/direction.tsx
  • src/components/ui/drawer.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/components/ui/empty.tsx
  • src/components/ui/field.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/input-otp.tsx
  • src/components/ui/input.tsx
  • src/components/ui/item.tsx
  • src/components/ui/kbd.tsx
  • src/components/ui/label.tsx
  • src/components/ui/menubar.tsx
  • src/components/ui/native-select.tsx
  • src/components/ui/navigation-menu.tsx
  • src/components/ui/pagination.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/progress.tsx
  • src/components/ui/radio-group.tsx
  • src/components/ui/resizable.tsx
  • src/components/ui/scroll-area.tsx
  • src/components/ui/select.tsx
  • src/components/ui/separator.tsx
  • src/components/ui/sheet.tsx
  • src/components/ui/sidebar.tsx
  • src/components/ui/skeleton.tsx
  • src/components/ui/slider.tsx
  • src/components/ui/sonner.tsx
  • src/components/ui/spinner.tsx
  • src/components/ui/switch.tsx
  • src/components/ui/table.tsx
  • src/components/ui/tabs.tsx
  • src/components/ui/textarea.tsx
  • src/components/ui/toggle-group.tsx
  • src/components/ui/toggle.tsx
  • src/components/ui/tooltip.tsx
  • src/hooks/use-controlled-state.tsx
  • src/hooks/use-mobile.ts
  • src/lib/get-strict-context.tsx
  • src/lib/utils.ts
  • src/locales/en/messages.po
  • src/locales/zh/messages.po
  • src/providers/index.tsx
  • src/utils/hero.ts
  • src/utils/random-name.ts
💤 Files with no reviewable changes (4)
  • src/utils/hero.ts
  • src/components/animate-ui/radix/switch.tsx
  • src/components/animate-ui/buttons/ripple.tsx
  • src/components/animate-ui/radix/tooltip.tsx

Comment thread components.json
Comment on lines +21 to +23
"registries": {
"@animate-ui": "https://animate-ui.com/r/{name}.json"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Spot-check a couple of likely registry entries used by this PR.
for name in components/base/tooltip components/base/switch components/buttons/ripple primitives/base/tooltip; do
  url="https://animate-ui.com/r/${name}.json"
  echo "GET ${url}"
  curl -sS -o /dev/null -w "  status=%{http_code}\n" "${url}"
done

Repository: Debbl/tools.aiwan.run

Length of output: 350


@animate-ui registry URL template is broken — returns 404 for all test endpoints.

The registry entry uses https://animate-ui.com/r/{name}.json, but spot-checking several likely component paths (e.g., components/base/tooltip, components/base/switch, primitives/base/tooltip) all return HTTP 404. The registry endpoint must be fixed before merging so that shadcn add commands don't fail for users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components.json` around lines 21 - 23, The `@animate-ui` registry entry is
using a broken URL template ("https://animate-ui.com/r/{name}.json") that
returns 404; update the "registries" mapping for the "@animate-ui" key to the
correct working template (or remove the entry) so shadcn add can resolve
components. Locate the "registries" object and change the value for
"@animate-ui" to the proper endpoint pattern (e.g., the actual host/path that
serves {name}.json), then verify by requesting a few known component names (like
components/base/tooltip) to ensure the template returns 200 before committing.

Comment thread package.json Outdated
Comment on lines +8 to +15
import { Switch } from '~/components/animate-ui/components/base/switch'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipPanel,
TooltipTrigger,
} from '~/components/animate-ui/radix/tooltip'
} from '~/components/animate-ui/components/base/tooltip'
import { RippleButton } from '~/components/animate-ui/components/buttons/ripple'
import { TooltipProvider } from '~/components/animate-ui/primitives/base/tooltip'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether components/base/tooltip re-exports TooltipProvider and that both
# files reference the same context source.
fd -p 'animate-ui/(components|primitives)/base/tooltip' -t f
fd -p 'animate-ui/(components|primitives)/base/tooltip' -t f -x rg -n -C2 'TooltipProvider|createContext|useContext' {}

Repository: Debbl/tools.aiwan.run

Length of output: 1912


Import TooltipProvider from ~/components/animate-ui/components/base/tooltip for consistency.

Tooltip, TooltipTrigger, and TooltipPanel are imported from components/base/tooltip, but TooltipProvider is sourced from primitives/base/tooltip. The components module exports its own TooltipProvider wrapper; import all tooltip parts from the same components/base/tooltip module to ensure they share the correct context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(locale)/[locale]/keep-screen-on/page.tsx around lines 8 - 15, The
TooltipProvider is being imported from primitives while Tooltip, TooltipTrigger,
and TooltipPanel come from components; change the import so TooltipProvider is
imported from the same module as the others (the components export) to ensure
they share the same context. Locate the imports for Tooltip, TooltipTrigger,
TooltipPanel and replace the TooltipProvider import to come from the same module
(the one that exports Tooltip, TooltipTrigger, TooltipPanel) so all tooltip
parts use the same provider.

Comment on lines +130 to +140
<SelectTrigger className='w-full'>
<SelectValue
placeholder={t`Separator`}
render={(props, state) => {
const option = separatorOptions.find(
(option) => option.value === state.value,
)

return <span {...props}>{option?.label}</span>
}}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Base UI react Select.Value render prop signature state.value v1.4

💡 Result:

In Base UI React v1.4 (released April 13, 2026), the Select.Value component renders a span element displaying the label of the currently selected item (when items prop is provided on Select.Root) or the raw value. The render prop (children function) signature is: (value: any) => React.ReactNode This formats the selected value. Example: <Select.Value> {(value) => value ? labels[value] : 'No value'} </Select.Value> SelectValueState interface (for render/className callbacks in some Base UI components): type SelectValueState = { value: any; placeholder: boolean; } No changes to Select.Value render prop in v1.4 release notes. Type definitions from @base-ui/react ~1.1.0 confirm the signature; recent PR #1562 (merged Mar 2025) passes value as second arg to children in some contexts, but primary render prop remains (value: any) => ReactNode. Official docs show single value param usage. Note: Base UI uses children as render prop (not separate render prop). For full state in render callbacks, use className={(state) => ...} where state includes value.

Citations:


Fix incorrect SelectValue render prop usage.

Base UI's Select.Value does not have a render prop. It uses a children function with signature (value: any) => React.ReactNode (single value parameter, not props + state). Replace the current implementation with:

<SelectValue placeholder={t`Separator`}>
  {(value) => {
    const option = separatorOptions.find(
      (option) => option.value === value,
    )
    return option?.label
  }}
</SelectValue>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(locale)/[locale]/random-name/page.tsx around lines 130 - 140, The
SelectValue usage is incorrect: Base UI's Select.Value expects a children
function with signature (value) => ReactNode, not a render prop; update the
SelectValue instance (the component currently using render) to remove the render
prop and instead pass a children function that receives the selected value,
lookup the matching option from separatorOptions (same symbol) by option.value
=== value, and return option?.label; preserve the placeholder={t`Separator`}
prop.

Comment thread src/app/globals.css
@@ -1,30 +1,10 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@import 'shadcn/tailwind.css';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the shadcn package exposes tailwind.css and inspect its package exports
fd -t d '^shadcn$' node_modules 2>/dev/null | head -5
fd 'tailwind.css' node_modules/shadcn 2>/dev/null
cat node_modules/shadcn/package.json 2>/dev/null | jq '{name, version, exports, files, main}'

Repository: Debbl/tools.aiwan.run

Length of output: 47


🏁 Script executed:

# Check if node_modules exists and list shadcn-related packages
ls -la node_modules 2>/dev/null | grep -i shadcn || echo "No shadcn in node_modules"
find node_modules -maxdepth 2 -name "*shadcn*" -type d 2>/dev/null | head -20

Repository: Debbl/tools.aiwan.run

Length of output: 91


🏁 Script executed:

# If shadcn exists, check its structure and package.json
if [ -d "node_modules/shadcn" ]; then
  echo "=== shadcn package structure ==="
  ls -la node_modules/shadcn/
  echo "=== package.json ==="
  cat node_modules/shadcn/package.json | head -50
fi

Repository: Debbl/tools.aiwan.run

Length of output: 47


🏁 Script executed:

# Check if the import is used elsewhere in the codebase
rg "shadcn/tailwind" --type css --type js --type ts --type tsx 2>/dev/null || echo "No matches found"

Repository: Debbl/tools.aiwan.run

Length of output: 82


🏁 Script executed:

# Look for any tailwind config to understand how tailwind is set up
fd -e "tailwind.config" -e "tailwind.config.ts" -e "tailwind.config.js" 2>/dev/null | head -5

Repository: Debbl/tools.aiwan.run

Length of output: 47


🏁 Script executed:

# Check if globals.css exists and show its contents
cat -n src/app/globals.css 2>/dev/null || echo "File not found"

Repository: Debbl/tools.aiwan.run

Length of output: 5655


🏁 Script executed:

# Check package.json to see declared dependencies
cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null | head -50

Repository: Debbl/tools.aiwan.run

Length of output: 1397


🏁 Script executed:

# Search for any tailwind or shadcn references in the project
rg "shadcn|@shadcn" --type json --type ts --type js --type css 2>/dev/null | head -20

Repository: Debbl/tools.aiwan.run

Length of output: 219


🌐 Web query:

shadcn v4.5.0 package exports tailwind.css

💡 Result:

No, the shadcn (CLI) package at version 4.5.0 (or latest available ~4.4.0 as of Apr 2026) does not export a tailwind.css file. shadcn/ui is not a traditional npm component package but a CLI tool (npx shadcn@latest) for copying unstyled React components into your project. Tailwind CSS styles are configured in your own globals.css file via manual installation instructions, e.g., @import "tailwindcss"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; but "shadcn/tailwind.css" refers to a registry or local file generated by the CLI, not an export from the shadcn npm package itself. The package structure includes index.js, index.d.ts, and tailwind.css (1.6KB), but no explicit "exports" map in package.json exposes tailwind.css for import like "./tailwind.css". Users must set up Tailwind separately and use the CLI to add components.

Citations:


🌐 Web query:

shadcn CLI tool tailwind css import path package structure

💡 Result:

The shadcn CLI (npx shadcn@latest init/add) configures Tailwind CSS integration via a components.json file created during init. It prompts for Tailwind paths: global CSS file (e.g., app/globals.css), tailwind.config.js location (e.g., tailwind.config.js; blank for Tailwind v4), prefix, baseColor, and cssVariables usage. These paths are stored in components.json under "tailwind": {"config": "...", "css": "...", ...}. The CLI uses them with tsconfig.json paths to place components and generate imports. Standard package structure (non-monorepo): - components/ui/ : UI primitives (e.g., components/ui/button.tsx) - lib/utils.ts : cn helper (clsx + tailwind-merge) - styles/globals.css : @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; plus CSS vars for theming Imports: import { Button } from "@/components/ui/button" (using @/ alias from tsconfig). In monorepos: Components in packages/ui/src/components/, app imports via "@workspace/ui/components/button". globals.css imports Tailwind directives and shadcn tailwind.css (manual install). CLI ensures content paths in tailwind.config.js include the components dir.

Citations:


Remove or fix the shadcn/tailwind.css import—it is not a valid export from the shadcn package.

The shadcn CLI package (v4.5.0) includes a tailwind.css file internally but does not expose it via the package's exports field. While the file may exist at node_modules/shadcn/tailwind.css, importing it directly with @import 'shadcn/tailwind.css' is not a documented or supported approach. Standard Tailwind v4 setup uses:

`@import` 'tailwindcss';
`@import` 'tw-animate-css';

Consider removing this line or clarifying its purpose if custom shadcn styling is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` at line 3, Remove the invalid import statement in
globals.css that reads "@import 'shadcn/tailwind.css';" because that file is not
exported by the shadcn package; either delete the line or replace it with the
standard Tailwind imports used by the project (use the Tailwind entry and any
animation plugin import you actually rely on), and if you need shadcn-specific
styles, generate or include them via the shadcn CLI workflow instead of
importing the unpublished package file.

Comment on lines +1 to +7
import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "~/lib/utils"
import { Separator } from "~/components/ui/separator"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does @base-ui/react useRender require a "use client" boundary in Next.js App Router?

💡 Result:

Yes, using useRender from @base-ui/react requires a "use client" boundary in Next.js App Router.

Citations:


🏁 Script executed:

# Check if the file has "use client" directive at the top
head -20 src/components/ui/item.tsx

Repository: Debbl/tools.aiwan.run

Length of output: 686


🏁 Script executed:

# Search for useRender usage in the file
rg "useRender" src/components/ui/item.tsx -B 2 -A 2

Repository: Debbl/tools.aiwan.run

Length of output: 449


🏁 Script executed:

# Check the comparison files mentioned
head -5 src/components/ui/select.tsx
head -5 src/components/ui/radio-group.tsx

Repository: Debbl/tools.aiwan.run

Length of output: 334


Add "use client" directive — useRender is a React hook.

Item calls useRender(...), which is a React hook that cannot be used in Server Components. In the Next.js App Router, this file will fail to render from a Server Component subtree without the "use client" boundary. Other hook-driven primitives in this PR (e.g., select.tsx, radio-group.tsx) declare "use client" for the same reason.

Proposed fix
+"use client"
+
 import * as React from "react"
 import { mergeProps } from "@base-ui/react/merge-props"
 import { useRender } from "@base-ui/react/use-render"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/lib/utils"
import { Separator } from "~/components/ui/separator"
"use client"
import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/lib/utils"
import { Separator } from "~/components/ui/separator"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/item.tsx` around lines 1 - 7, This file uses the React hook
useRender inside the Item component, so add the "use client" directive as the
very first line of src/components/ui/item.tsx to mark it a client component;
ensure the string literal "use client" appears before any imports so hooks
(useRender) can be used safely (follow the same pattern used in select.tsx and
radio-group.tsx).

<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-2 pr-2.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Tailwind CSS v4 syntax for chaining a boolean data variant with a value-based data variant (e.g. data-ending-style with data-[activation-direction=left])

💡 Result:

In Tailwind CSS v4, you chain a boolean data variant (existence of attribute, e.g. data-active) with a value-based data variant (specific value, e.g. data-[activation-direction=left]) by simply juxtaposing the variant prefixes before the utility class. Syntax: data-[activation-direction=left]:data-active:bg-green-500 This generates CSS targeting elements where both data-activation-direction="left" AND data-active is present. For the example with data-ending-style (assuming boolean like data-ending-style present, and data-[activation-direction=left]): data-[activation-direction=left]:data-ending-style:bg-blue-500 This works due to v4's composable variants support, where data-* variants (both boolean like data-active: and value-based like data-[foo=bar]:) can be chained with other variants including each other, group-, peer-, etc. Examples from docs and beta: - data-selected:opacity-100 (boolean data-selected) - data-[size=large]:p-8 (value-based) - Chaining examples like group-data-[size=large]:p-8, not-data-active:underline, etc. No configuration needed; dynamic data-* variants work out-of-the-box in v4.

Citations:


🏁 Script executed:

fd navigation-menu.tsx --type f

Repository: Debbl/tools.aiwan.run

Length of output: 103


🏁 Script executed:

cat -n src/components/ui/navigation-menu.tsx | sed -n '80,95p'

Repository: Debbl/tools.aiwan.run

Length of output: 2114


🏁 Script executed:

# Search for context on how data-ending-style and data-activation-direction are used
rg "data-ending-style|data-activation-direction" -A 2 -B 2 --type tsx --type ts

Repository: Debbl/tools.aiwan.run

Length of output: 93


🏁 Script executed:

rg "data-ending-style|data-activation-direction" -A 2 -B 2

Repository: Debbl/tools.aiwan.run

Length of output: 5127


🏁 Script executed:

# Verify the chaining pattern in sheet.tsx more systematically
cat -n src/components/ui/sheet.tsx | sed -n '35,48p'

Repository: Debbl/tools.aiwan.run

Length of output: 393


🏁 Script executed:

# Get the exact line with the sheet.tsx pattern to confirm the chaining order
cat -n src/components/ui/sheet.tsx | grep -A 1 "data-\[side" | head -20

Repository: Debbl/tools.aiwan.run

Length of output: 1290


🏁 Script executed:

# Double-check the navigation-menu.tsx Popup element pattern for consistency
cat -n src/components/ui/navigation-menu.tsx | sed -n '100,110p'

Repository: Debbl/tools.aiwan.run

Length of output: 440


🏁 Script executed:

# Find the Popup className in navigation-menu.tsx
cat -n src/components/ui/navigation-menu.tsx | sed -n '115,130p'

Repository: Debbl/tools.aiwan.run

Length of output: 1118


Incorrect Tailwind variant chaining order in the suggested fix.

The bracketing issue is correctly identified—value-based data attributes must use brackets (data-[activation-direction=left]: not data-activation-direction=left:). However, the suggested fix has the chaining order backwards. In Tailwind v4, value-based variants must come before boolean variants. The pattern used elsewhere in the codebase (e.g., sheet.tsx) confirms this: data-[side=bottom]:data-ending-style:... not data-ending-style:data-[side=bottom]:....

The four translate classes should be:

-        "data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] ...",
+        "data-[activation-direction=left]:data-ending-style:translate-x-[50%] data-[activation-direction=right]:data-ending-style:translate-x-[-50%] data-[activation-direction=left]:data-starting-style:translate-x-[-50%] data-[activation-direction=right]:data-starting-style:translate-x-[50%] ...",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/navigation-menu.tsx` at line 87, The Tailwind variant
chaining order in the navigation-menu class string is wrong: value-based data
variants must come before boolean variants, so replace patterns like
data-ending-style:data-activation-direction=left:... with
data-[activation-direction=left]:data-ending-style:... (and likewise for the
other three translate cases) in the class on the NavigationMenu/slot usage in
src/components/ui/navigation-menu.tsx; ensure you follow the established pattern
used in sheet.tsx (e.g., data-[side=bottom]:data-ending-style:...) so the four
translate classes use
data-[activation-direction=left/right]:data-ending-style:... and
data-[activation-direction=left/right]:data-starting-style:... ordering.

Comment on lines +140 to +156
function NavigationMenuIndicator({
className,
...props
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) {
return (
<NavigationMenuPrimitive.Icon
data-slot="navigation-menu-indicator"
className={cn(
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Icon>
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does @base-ui-components/react navigation-menu expose an Indicator primitive equivalent to Radix's NavigationMenu.Indicator, and what data-state attributes does NavigationMenu.Icon emit?

💡 Result:

No, @base-ui-components/react navigation-menu does not expose an Indicator primitive equivalent to Radix's NavigationMenu.Indicator. The official documentation lists components like Root, List, Item, Trigger, Icon, Content, Portal, Backdrop, Positioner, Popup, Arrow, and Viewport, but no Indicator is mentioned for active item highlighting. NavigationMenu.Icon emits data attributes based on its open state. The source code shows it uses triggerOpenStateMapping, which applies standard open/closed attributes like data-state="open" or data-state="closed" when the associated menu item is active and open. It renders as a span with aria-hidden="true" containing a ▼ icon, updating its state when the popup is open for that item.

Citations:


NavigationMenuIndicator is wired to the wrong primitive.

NavigationMenuPrimitive.Icon in Base UI is the dropdown chevron rendered inside Trigger ("an icon that indicates that the trigger button opens a menu") — it is not the Radix-style active-trigger indicator. Consequences:

  • It expects to be a child of Trigger, not a sibling positioned at top-full. Rendering it outside a Trigger will not produce the active-tracker arrow this file implies.
  • The data-[state=hidden] / data-[state=visible] animation selectors on line 148 will not match: Base UI's Icon emits data-state="open" or data-state="closed" based on the menu's open state, not the hidden/visible variants you're targeting. The in/out animations will never trigger.
  • This export name (NavigationMenuIndicator) will mislead consumers who expect Radix semantics.

Base UI's navigation menu does not ship an active-trigger Indicator primitive—the active-content reveal is handled via Viewport/Popup transitions on Positioner. Either remove NavigationMenuIndicator entirely or build it from Positioner/custom logic; do not alias Icon to it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/navigation-menu.tsx` around lines 140 - 156, The
NavigationMenuIndicator component is incorrectly using
NavigationMenuPrimitive.Icon (meant as a Trigger child) and wrong data-state
selectors; remove or replace it: either delete the NavigationMenuIndicator
export and any imports/uses referencing it, or re-implement it using the menu
Positioner/Viewport/Popup (or custom logic) to render the active-trigger arrow
and use the correct state attributes (Positioner/Viewport-driven transitions)
instead of NavigationMenuPrimitive.Icon and the data-[state=hidden|visible]
selectors; update references to the component accordingly and ensure the Trigger
children continue to use NavigationMenuPrimitive.Icon for chevrons.

Comment on lines +8 to +24
function Tabs({
className,
orientation = "horizontal",
...props
}: TabsPrimitive.Root.Props) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
className={cn(
"group/tabs flex gap-2 data-horizontal:flex-col",
className
)}
{...props}
/>
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does @base-ui/reactTabs.Root require anorientation prop for keyboard navigation and ARIA, or does it auto-detect from data-orientation?

💡 Result:

No, @base-ui/react Tabs.Root does not require an orientation prop for keyboard navigation and ARIA attributes. The prop is optional with a default value of 'horizontal'. When omitted, it uses horizontal orientation, which sets data-orientation="horizontal" on relevant elements and handles keyboard navigation accordingly (e.g., left/right arrows). For vertical orientation, explicitly provide orientation="vertical". There is no auto-detection from data-orientation or CSS layout; orientation is controlled solely by the prop.

Citations:


🏁 Script executed:

cat -n src/components/ui/tabs.tsx | head -30

Repository: Debbl/tools.aiwan.run

Length of output: 1087


orientation is destructured but never forwarded to TabsPrimitive.Root.

orientation is pulled out of the props bag on line 10 and only echoed back as a DOM data-orientation attribute on line 16. It is not passed to the underlying <TabsPrimitive.Root>, so the primitive always uses its default orientation. Two consequences:

  • Keyboard navigation stays horizontal even for orientation="vertical" (Left/Right vs Up/Down).
  • ARIA aria-orientation and the primitive's own data-orientation won't match the visual layout, and conflict with the manually set data-orientation (last-write-wins on the same DOM attribute).

The hand-rolled data-orientation on the wrapper becomes redundant once the primitive receives the orientation prop and sets it correctly.

🛠 Proposed fix
 function Tabs({
   className,
   orientation = "horizontal",
   ...props
 }: TabsPrimitive.Root.Props) {
   return (
     <TabsPrimitive.Root
       data-slot="tabs"
+      orientation={orientation}
-      data-orientation={orientation}
       className={cn(
         "group/tabs flex gap-2 data-horizontal:flex-col",
         className
       )}
       {...props}
     />
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/tabs.tsx` around lines 8 - 24, The Tabs component
destructures orientation but fails to pass it into TabsPrimitive.Root, so
forward the orientation prop into TabsPrimitive.Root (e.g., add
orientation={orientation}) and remove the manual data-orientation attribute from
the wrapper; ensure TabsPrimitive.Root receives the original orientation value
so keyboard/ARIA orientation and the primitive's internal state are correct
(update references inside the Tabs function around TabsPrimitive.Root and the
props spread).

Comment on lines +322 to 325
#: src/app/(locale)/[locale]/random-name/page.tsx:102
#: src/app/(locale)/[locale]/random-name/page.tsx:103
msgid "Underscore"
msgstr "VSIX URL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Underscore is mistranslated to VSIX URL.

The English msgstr for the “Underscore” key is "VSIX URL", which will surface in the UI wherever the random-name page renders the “Underscore” option label. Almost certainly a paste error from the nearby VSIX URL entry.

💚 Proposed fix
 #: src/app/(locale)/[locale]/random-name/page.tsx:102
 #: src/app/(locale)/[locale]/random-name/page.tsx:103
 msgid "Underscore"
-msgstr "VSIX URL"
+msgstr "Underscore"

Worth re-running lingui extract/compile to make sure no other msgstr got similarly stale.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#: src/app/(locale)/[locale]/random-name/page.tsx:102
#: src/app/(locale)/[locale]/random-name/page.tsx:103
msgid "Underscore"
msgstr "VSIX URL"
#: src/app/(locale)/[locale]/random-name/page.tsx:102
#: src/app/(locale)/[locale]/random-name/page.tsx:103
msgid "Underscore"
msgstr "Underscore"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/locales/en/messages.po` around lines 322 - 325, The PO entry for msgid
"Underscore" is incorrectly translated as "VSIX URL"; update the msgstr for that
entry to the correct English label "Underscore" (the msgid "Underscore" in
src/locales/en/messages.po), then re-run your localization pipeline (e.g.,
lingui extract/compile) so compiled catalogs reflect the fix and ensure no other
msgstrs were accidentally pasted from the VSIX URL entry referenced by
src/app/(locale)/[locale]/random-name/page.tsx.

@netlify

netlify Bot commented May 15, 2026

Copy link
Copy Markdown

Deploy Preview for tools-aiwan-run ready!

Name Link
🔨 Latest commit e1202ed
🔍 Latest deploy log https://app.netlify.com/projects/tools-aiwan-run/deploys/6a0701bf99beb700abb6e54e
😎 Deploy Preview https://deploy-preview-136--tools-aiwan-run.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Debbl Debbl merged commit d45e417 into main May 15, 2026
7 checks passed
@Debbl Debbl deleted the refactor/all-in-shadcn-ui branch May 15, 2026 11:23
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